From 07d10d4d4e62f77c309788b91e6f151c16c56181 Mon Sep 17 00:00:00 2001 From: Ivailo Monev Date: Tue, 18 Nov 2014 17:46:34 +0000 Subject: [PATCH] initial import of ark --- ark/.reviewboardrc | 4 + ark/CMakeLists.txt | 50 + ark/COPYING | 346 ++++++ ark/COPYING.icons | 216 ++++ ark/CTestConfig.cmake | 13 + ark/CTestCustom.cmake.in | 1 + ark/HACKING | 52 + ark/Mainpage.dox | 8 + ark/Messages.sh | 6 + ark/app/CMakeLists.txt | 50 + ark/app/ark.appdata.xml | 226 ++++ ark/app/ark.desktop.cmake | 158 +++ ark/app/ark_addtoservicemenu.desktop | 351 +++++++ ark/app/ark_dndextract.desktop.cmake | 61 ++ ark/app/ark_servicemenu.desktop.cmake | 238 +++++ ark/app/arkui.rc | 17 + ark/app/batchextract.cpp | 292 ++++++ ark/app/batchextract.h | 234 +++++ ark/app/extractHereDndPlugin.cpp | 82 ++ ark/app/extractHereDndPlugin.h | 46 + ark/app/icons/CMakeLists.txt | 1 + ark/app/icons/hi128-apps-ark.png | Bin 0 -> 13215 bytes ark/app/icons/hi16-apps-ark.png | Bin 0 -> 528 bytes ark/app/icons/hi22-apps-ark.png | Bin 0 -> 1069 bytes ark/app/icons/hi32-apps-ark.png | Bin 0 -> 1718 bytes ark/app/icons/hi48-apps-ark.png | Bin 0 -> 3245 bytes ark/app/icons/hi64-apps-ark.png | Bin 0 -> 4996 bytes ark/app/icons/hisc-apps-ark.svgz | Bin 0 -> 53669 bytes ark/app/main.cpp | 210 ++++ ark/app/mainwindow.cpp | 259 +++++ ark/app/mainwindow.h | 66 ++ ark/cmake/modules/COPYING-CMAKE-SCRIPTS | 22 + ark/cmake/modules/FindLibArchive.cmake | 46 + ark/config.h.cmake | 2 + ark/doc/CMakeLists.txt | 5 + ark/doc/ark-mainwindow.png | Bin 0 -> 53839 bytes ark/doc/index.docbook | 336 ++++++ ark/doc/man-ark.1.docbook | 208 ++++ ark/kerfuffle/CMakeLists.txt | 50 + ark/kerfuffle/adddialog.cpp | 135 +++ ark/kerfuffle/adddialog.h | 63 ++ ark/kerfuffle/adddialog.ui | 113 ++ ark/kerfuffle/addtoarchive.cpp | 213 ++++ ark/kerfuffle/addtoarchive.h | 85 ++ ark/kerfuffle/archive.cpp | 333 ++++++ ark/kerfuffle/archive.h | 149 +++ ark/kerfuffle/archiveinterface.cpp | 122 +++ ark/kerfuffle/archiveinterface.h | 138 +++ ark/kerfuffle/ark.kcfg | 25 + ark/kerfuffle/cliinterface.cpp | 751 +++++++++++++ ark/kerfuffle/cliinterface.h | 349 +++++++ ark/kerfuffle/extractiondialog.cpp | 234 +++++ ark/kerfuffle/extractiondialog.h | 78 ++ ark/kerfuffle/extractiondialog.ui | 220 ++++ ark/kerfuffle/jobs.cpp | 346 ++++++ ark/kerfuffle/jobs.h | 171 +++ ark/kerfuffle/kerfufflePlugin.desktop | 74 ++ ark/kerfuffle/kerfuffle_export.h | 53 + ark/kerfuffle/queries.cpp | 204 ++++ ark/kerfuffle/queries.h | 111 ++ ark/kerfuffle/settings.kcfgc | 7 + ark/kerfuffle/tests/CMakeLists.txt | 21 + ark/kerfuffle/tests/archivetest.cpp | 76 ++ .../data/archive-deepsinglehierarchy.json | 20 + .../tests/data/archive-multiplefolders.json | 16 + .../tests/data/archive-nodir-manyfiles.json | 8 + .../tests/data/archive-onetopfolder.json | 9 + .../tests/data/archive-password.json | 13 + .../tests/data/archive-singlefile.json | 5 + .../data/archive-unorderedsinglefolder.json | 16 + ark/kerfuffle/tests/data/archive001.json | 15 + ark/kerfuffle/tests/data/archive002.json | 18 + .../tests/data/archivetest_encrypted.zip | Bin 0 -> 196 bytes .../tests/data/archivetest_unencrypted.zip | Bin 0 -> 168 bytes ark/kerfuffle/tests/data/simplearchive.tar.gz | Bin 0 -> 197 bytes ark/kerfuffle/tests/jobstest.cpp | 366 +++++++ ark/kerfuffle/tests/jsonarchiveinterface.cpp | 109 ++ ark/kerfuffle/tests/jsonarchiveinterface.h | 67 ++ ark/kerfuffle/tests/jsonparser.cpp | 133 +++ ark/kerfuffle/tests/jsonparser.h | 82 ++ ark/part/CMakeLists.txt | 30 + ark/part/archivemodel.cpp | 986 ++++++++++++++++++ ark/part/archivemodel.h | 126 +++ ark/part/archiveview.cpp | 142 +++ ark/part/archiveview.h | 54 + ark/part/ark_part.desktop.cmake | 152 +++ ark/part/ark_part.rc | 28 + ark/part/arkviewer.cpp | 266 +++++ ark/part/arkviewer.h | 63 ++ ark/part/dnddbusinterface.xml | 8 + ark/part/infopanel.cpp | 213 ++++ ark/part/infopanel.h | 76 ++ ark/part/infopanel.ui | 126 +++ ark/part/interface.h | 37 + ark/part/jobtracker.cpp | 107 ++ ark/part/jobtracker.h | 68 ++ ark/part/jobtracker.ui | 92 ++ ark/part/part.cpp | 916 ++++++++++++++++ ark/part/part.h | 126 +++ ark/plugins/CLI-README | 21 + ark/plugins/CMakeLists.txt | 28 + ark/plugins/cli7zplugin/CMakeLists.txt | 21 + ark/plugins/cli7zplugin/cliplugin.cpp | 180 ++++ ark/plugins/cli7zplugin/cliplugin.h | 61 ++ .../cli7zplugin/kerfuffle_cli7z.desktop.cmake | 69 ++ ark/plugins/clilhaplugin/CMakeLists.txt | 21 + ark/plugins/clilhaplugin/cliplugin.cpp | 144 +++ ark/plugins/clilhaplugin/cliplugin.h | 49 + .../kerfuffle_clilha.desktop.cmake | 62 ++ ark/plugins/cliplugin-example/CMakeLists.txt | 7 + ark/plugins/cliplugin-example/cliplugin.cpp | 148 +++ ark/plugins/cliplugin-example/cliplugin.h | 45 + .../cliplugin-example/kerfuffle_cli.desktop | 66 ++ ark/plugins/clirarplugin/CMakeLists.txt | 23 + ark/plugins/clirarplugin/cliplugin.cpp | 362 +++++++ ark/plugins/clirarplugin/cliplugin.h | 65 ++ .../kerfuffle_clirar.desktop.cmake | 69 ++ ark/plugins/clirarplugin/tests/CMakeLists.txt | 4 + ark/plugins/clirarplugin/tests/clirartest.cpp | 86 ++ ark/plugins/clirarplugin/tests/clirartest.h | 40 + .../tests/data/testReadArchiveWithSymlink.txt | 22 + .../tests/data/testReadCorruptedArchive.txt | 23 + ark/plugins/clizipplugin/CMakeLists.txt | 21 + ark/plugins/clizipplugin/cliplugin.cpp | 143 +++ ark/plugins/clizipplugin/cliplugin.h | 47 + .../kerfuffle_clizip.desktop.cmake | 69 ++ ark/plugins/karchiveplugin/CMakeLists.txt | 21 + ark/plugins/karchiveplugin/karchiveplugin.cpp | 331 ++++++ ark/plugins/karchiveplugin/karchiveplugin.h | 70 ++ .../kerfuffle_karchive.desktop.cmake | 129 +++ ark/plugins/libarchive/CMakeLists.txt | 41 + .../kerfuffle_libarchive.desktop.cmake | 130 +++ ...erfuffle_libarchive_readonly.desktop.cmake | 66 ++ ark/plugins/libarchive/libarchivehandler.cpp | 791 ++++++++++++++ ark/plugins/libarchive/libarchivehandler.h | 75 ++ .../libsinglefileplugin/CMakeLists.txt | 69 ++ ark/plugins/libsinglefileplugin/bz2plugin.cpp | 44 + ark/plugins/libsinglefileplugin/bz2plugin.h | 40 + ark/plugins/libsinglefileplugin/gzplugin.cpp | 44 + ark/plugins/libsinglefileplugin/gzplugin.h | 40 + .../kerfuffle_libbz2.desktop | 124 +++ .../kerfuffle_libgz.desktop | 126 +++ .../kerfuffle_libxz.desktop | 124 +++ .../libsinglefileplugin/singlefileplugin.cpp | 160 +++ .../libsinglefileplugin/singlefileplugin.h | 50 + ark/plugins/libsinglefileplugin/xzplugin.cpp | 45 + ark/plugins/libsinglefileplugin/xzplugin.h | 40 + 147 files changed, 16676 insertions(+) create mode 100644 ark/.reviewboardrc create mode 100644 ark/CMakeLists.txt create mode 100644 ark/COPYING create mode 100644 ark/COPYING.icons create mode 100644 ark/CTestConfig.cmake create mode 100644 ark/CTestCustom.cmake.in create mode 100644 ark/HACKING create mode 100644 ark/Mainpage.dox create mode 100644 ark/Messages.sh create mode 100644 ark/app/CMakeLists.txt create mode 100644 ark/app/ark.appdata.xml create mode 100755 ark/app/ark.desktop.cmake create mode 100644 ark/app/ark_addtoservicemenu.desktop create mode 100644 ark/app/ark_dndextract.desktop.cmake create mode 100644 ark/app/ark_servicemenu.desktop.cmake create mode 100644 ark/app/arkui.rc create mode 100644 ark/app/batchextract.cpp create mode 100644 ark/app/batchextract.h create mode 100644 ark/app/extractHereDndPlugin.cpp create mode 100644 ark/app/extractHereDndPlugin.h create mode 100644 ark/app/icons/CMakeLists.txt create mode 100644 ark/app/icons/hi128-apps-ark.png create mode 100644 ark/app/icons/hi16-apps-ark.png create mode 100644 ark/app/icons/hi22-apps-ark.png create mode 100644 ark/app/icons/hi32-apps-ark.png create mode 100644 ark/app/icons/hi48-apps-ark.png create mode 100644 ark/app/icons/hi64-apps-ark.png create mode 100644 ark/app/icons/hisc-apps-ark.svgz create mode 100644 ark/app/main.cpp create mode 100644 ark/app/mainwindow.cpp create mode 100644 ark/app/mainwindow.h create mode 100644 ark/cmake/modules/COPYING-CMAKE-SCRIPTS create mode 100644 ark/cmake/modules/FindLibArchive.cmake create mode 100644 ark/config.h.cmake create mode 100644 ark/doc/CMakeLists.txt create mode 100644 ark/doc/ark-mainwindow.png create mode 100644 ark/doc/index.docbook create mode 100644 ark/doc/man-ark.1.docbook create mode 100644 ark/kerfuffle/CMakeLists.txt create mode 100644 ark/kerfuffle/adddialog.cpp create mode 100644 ark/kerfuffle/adddialog.h create mode 100644 ark/kerfuffle/adddialog.ui create mode 100644 ark/kerfuffle/addtoarchive.cpp create mode 100644 ark/kerfuffle/addtoarchive.h create mode 100644 ark/kerfuffle/archive.cpp create mode 100644 ark/kerfuffle/archive.h create mode 100644 ark/kerfuffle/archiveinterface.cpp create mode 100644 ark/kerfuffle/archiveinterface.h create mode 100644 ark/kerfuffle/ark.kcfg create mode 100644 ark/kerfuffle/cliinterface.cpp create mode 100644 ark/kerfuffle/cliinterface.h create mode 100644 ark/kerfuffle/extractiondialog.cpp create mode 100644 ark/kerfuffle/extractiondialog.h create mode 100644 ark/kerfuffle/extractiondialog.ui create mode 100644 ark/kerfuffle/jobs.cpp create mode 100644 ark/kerfuffle/jobs.h create mode 100644 ark/kerfuffle/kerfufflePlugin.desktop create mode 100644 ark/kerfuffle/kerfuffle_export.h create mode 100644 ark/kerfuffle/queries.cpp create mode 100644 ark/kerfuffle/queries.h create mode 100644 ark/kerfuffle/settings.kcfgc create mode 100644 ark/kerfuffle/tests/CMakeLists.txt create mode 100644 ark/kerfuffle/tests/archivetest.cpp create mode 100644 ark/kerfuffle/tests/data/archive-deepsinglehierarchy.json create mode 100644 ark/kerfuffle/tests/data/archive-multiplefolders.json create mode 100644 ark/kerfuffle/tests/data/archive-nodir-manyfiles.json create mode 100644 ark/kerfuffle/tests/data/archive-onetopfolder.json create mode 100644 ark/kerfuffle/tests/data/archive-password.json create mode 100644 ark/kerfuffle/tests/data/archive-singlefile.json create mode 100644 ark/kerfuffle/tests/data/archive-unorderedsinglefolder.json create mode 100644 ark/kerfuffle/tests/data/archive001.json create mode 100644 ark/kerfuffle/tests/data/archive002.json create mode 100644 ark/kerfuffle/tests/data/archivetest_encrypted.zip create mode 100644 ark/kerfuffle/tests/data/archivetest_unencrypted.zip create mode 100644 ark/kerfuffle/tests/data/simplearchive.tar.gz create mode 100644 ark/kerfuffle/tests/jobstest.cpp create mode 100644 ark/kerfuffle/tests/jsonarchiveinterface.cpp create mode 100644 ark/kerfuffle/tests/jsonarchiveinterface.h create mode 100644 ark/kerfuffle/tests/jsonparser.cpp create mode 100644 ark/kerfuffle/tests/jsonparser.h create mode 100644 ark/part/CMakeLists.txt create mode 100644 ark/part/archivemodel.cpp create mode 100644 ark/part/archivemodel.h create mode 100644 ark/part/archiveview.cpp create mode 100644 ark/part/archiveview.h create mode 100644 ark/part/ark_part.desktop.cmake create mode 100644 ark/part/ark_part.rc create mode 100644 ark/part/arkviewer.cpp create mode 100644 ark/part/arkviewer.h create mode 100644 ark/part/dnddbusinterface.xml create mode 100644 ark/part/infopanel.cpp create mode 100644 ark/part/infopanel.h create mode 100644 ark/part/infopanel.ui create mode 100644 ark/part/interface.h create mode 100644 ark/part/jobtracker.cpp create mode 100644 ark/part/jobtracker.h create mode 100644 ark/part/jobtracker.ui create mode 100644 ark/part/part.cpp create mode 100644 ark/part/part.h create mode 100644 ark/plugins/CLI-README create mode 100644 ark/plugins/CMakeLists.txt create mode 100644 ark/plugins/cli7zplugin/CMakeLists.txt create mode 100644 ark/plugins/cli7zplugin/cliplugin.cpp create mode 100644 ark/plugins/cli7zplugin/cliplugin.h create mode 100644 ark/plugins/cli7zplugin/kerfuffle_cli7z.desktop.cmake create mode 100644 ark/plugins/clilhaplugin/CMakeLists.txt create mode 100644 ark/plugins/clilhaplugin/cliplugin.cpp create mode 100644 ark/plugins/clilhaplugin/cliplugin.h create mode 100644 ark/plugins/clilhaplugin/kerfuffle_clilha.desktop.cmake create mode 100644 ark/plugins/cliplugin-example/CMakeLists.txt create mode 100644 ark/plugins/cliplugin-example/cliplugin.cpp create mode 100644 ark/plugins/cliplugin-example/cliplugin.h create mode 100644 ark/plugins/cliplugin-example/kerfuffle_cli.desktop create mode 100644 ark/plugins/clirarplugin/CMakeLists.txt create mode 100644 ark/plugins/clirarplugin/cliplugin.cpp create mode 100644 ark/plugins/clirarplugin/cliplugin.h create mode 100644 ark/plugins/clirarplugin/kerfuffle_clirar.desktop.cmake create mode 100644 ark/plugins/clirarplugin/tests/CMakeLists.txt create mode 100644 ark/plugins/clirarplugin/tests/clirartest.cpp create mode 100644 ark/plugins/clirarplugin/tests/clirartest.h create mode 100644 ark/plugins/clirarplugin/tests/data/testReadArchiveWithSymlink.txt create mode 100644 ark/plugins/clirarplugin/tests/data/testReadCorruptedArchive.txt create mode 100644 ark/plugins/clizipplugin/CMakeLists.txt create mode 100644 ark/plugins/clizipplugin/cliplugin.cpp create mode 100644 ark/plugins/clizipplugin/cliplugin.h create mode 100644 ark/plugins/clizipplugin/kerfuffle_clizip.desktop.cmake create mode 100644 ark/plugins/karchiveplugin/CMakeLists.txt create mode 100644 ark/plugins/karchiveplugin/karchiveplugin.cpp create mode 100644 ark/plugins/karchiveplugin/karchiveplugin.h create mode 100644 ark/plugins/karchiveplugin/kerfuffle_karchive.desktop.cmake create mode 100644 ark/plugins/libarchive/CMakeLists.txt create mode 100644 ark/plugins/libarchive/kerfuffle_libarchive.desktop.cmake create mode 100644 ark/plugins/libarchive/kerfuffle_libarchive_readonly.desktop.cmake create mode 100644 ark/plugins/libarchive/libarchivehandler.cpp create mode 100644 ark/plugins/libarchive/libarchivehandler.h create mode 100644 ark/plugins/libsinglefileplugin/CMakeLists.txt create mode 100644 ark/plugins/libsinglefileplugin/bz2plugin.cpp create mode 100644 ark/plugins/libsinglefileplugin/bz2plugin.h create mode 100644 ark/plugins/libsinglefileplugin/gzplugin.cpp create mode 100644 ark/plugins/libsinglefileplugin/gzplugin.h create mode 100644 ark/plugins/libsinglefileplugin/kerfuffle_libbz2.desktop create mode 100644 ark/plugins/libsinglefileplugin/kerfuffle_libgz.desktop create mode 100644 ark/plugins/libsinglefileplugin/kerfuffle_libxz.desktop create mode 100644 ark/plugins/libsinglefileplugin/singlefileplugin.cpp create mode 100644 ark/plugins/libsinglefileplugin/singlefileplugin.h create mode 100644 ark/plugins/libsinglefileplugin/xzplugin.cpp create mode 100644 ark/plugins/libsinglefileplugin/xzplugin.h diff --git a/ark/.reviewboardrc b/ark/.reviewboardrc new file mode 100644 index 00000000..2056db27 --- /dev/null +++ b/ark/.reviewboardrc @@ -0,0 +1,4 @@ +REPOSITORY = "git://anongit.kde.org/ark" +REVIEWBOARD_URL = "https://git.reviewboard.kde.org" +TARGET_GROUPS = "kdeutils" +TARGET_PEOPLE = "rkcosta" diff --git a/ark/CMakeLists.txt b/ark/CMakeLists.txt new file mode 100644 index 00000000..db467038 --- /dev/null +++ b/ark/CMakeLists.txt @@ -0,0 +1,50 @@ +project(ark) + +include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) + +if(${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_CURRENT_SOURCE_DIR}) + find_package(KDE4) + include( KDE4Defaults ) + include_directories(${KDE4_INCLUDES}) + + add_definitions(${QT_DEFINITIONS} ${KDE4_DEFINITIONS}) + add_definitions(-DQT_USE_FAST_CONCATENATION -DQT_USE_FAST_OPERATOR_PLUS) + + # If definitions like -D_GNU_SOURCE are needed for these checks they + # should be added to _KDE4_PLATFORM_DEFINITIONS when it is originally + # defined outside this file. Here we include these definitions in + # CMAKE_REQUIRED_DEFINITIONS so they will be included in the build of + # checks below. + set( CMAKE_REQUIRED_DEFINITIONS ${_KDE4_PLATFORM_DEFINITIONS} ) +endif() + +include( MacroLibrary ) +list(APPEND CMAKE_MODULE_PATH ${ark_SOURCE_DIR}/cmake/modules) + +macro_optional_find_package(LibArchive) +macro_log_feature(LIBARCHIVE_FOUND "LibArchive" "A library for dealing with a wide variety of archive file formats" "http://code.google.com/p/libarchive/" FALSE "" "Required for among others tar, tar.gz, tar.bz2 formats in Ark.") + +configure_file(config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config.h) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/CTestCustom.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/CTestCustom.cmake) + +add_definitions(-DQT_NO_CAST_FROM_ASCII) + +option(WITH_TEST_COVERAGE "Build with test coverage support" OFF) +if (WITH_TEST_COVERAGE) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} --coverage") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --coverage") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --coverage") +endif (WITH_TEST_COVERAGE) + +set(SUPPORTED_ARK_MIMETYPES "") + +add_subdirectory(plugins) +add_subdirectory(kerfuffle) +add_subdirectory(part) +add_subdirectory(app) +add_subdirectory(doc) + +if(${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_CURRENT_SOURCE_DIR}) + message(STATUS "Supported MIME types: ${SUPPORTED_ARK_MIMETYPES}") + macro_display_feature_log() +endif(${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/ark/COPYING b/ark/COPYING new file mode 100644 index 00000000..5185fd3f --- /dev/null +++ b/ark/COPYING @@ -0,0 +1,346 @@ +NOTE! The GPL below is copyrighted by the Free Software Foundation, but +the instance of code that it refers to (the kde programs) are copyrighted +by the authors who actually wrote it. + +--------------------------------------------------------------------------- + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) 19yy + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 19yy name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/ark/COPYING.icons b/ark/COPYING.icons new file mode 100644 index 00000000..045b62e8 --- /dev/null +++ b/ark/COPYING.icons @@ -0,0 +1,216 @@ +The Oxygen Icon Theme + Copyright (C) 2007 Nuno Pinheiro + Copyright (C) 2007 David Vignoni + Copyright (C) 2007 David Miller + Copyright (C) 2007 Johann Ollivier Lapeyre + Copyright (C) 2007 Kenneth Wimer + Copyright (C) 2007 Riccardo Iaconelli + + +and others + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see . + +Clarification: + + The GNU Lesser General Public License or LGPL is written for + software libraries in the first place. We expressly want the LGPL to + be valid for this artwork library too. + + KDE Oxygen theme icons is a special kind of software library, it is an + artwork library, it's elements can be used in a Graphical User Interface, or + GUI. + + Source code, for this library means: + - where they exist, SVG; + - otherwise, if applicable, the multi-layered formats xcf or psd, or + otherwise png. + + The LGPL in some sections obliges you to make the files carry + notices. With images this is in some cases impossible or hardly useful. + + With this library a notice is placed at a prominent place in the directory + containing the elements. You may follow this practice. + + The exception in section 5 of the GNU Lesser General Public License covers + the use of elements of this art library in a GUI. + + kde-artists [at] kde.org + +----- + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/ark/CTestConfig.cmake b/ark/CTestConfig.cmake new file mode 100644 index 00000000..8bd9c156 --- /dev/null +++ b/ark/CTestConfig.cmake @@ -0,0 +1,13 @@ +## This file should be placed in the root directory of your project. +## Then modify the CMakeLists.txt file in the root directory of your +## project to incorporate the testing dashboard. +## # The following are required to uses Dart and the Cdash dashboard +## ENABLE_TESTING() +## INCLUDE(CTest) +set(CTEST_PROJECT_NAME "ark") +set(CTEST_NIGHTLY_START_TIME "20:00:00 CET") + +set(CTEST_DROP_METHOD "http") +set(CTEST_DROP_SITE "my.cdash.org") +set(CTEST_DROP_LOCATION "/submit.php?project=ark") +set(CTEST_DROP_SITE_CDASH TRUE) diff --git a/ark/CTestCustom.cmake.in b/ark/CTestCustom.cmake.in new file mode 100644 index 00000000..130d3df8 --- /dev/null +++ b/ark/CTestCustom.cmake.in @@ -0,0 +1 @@ +set(CTEST_CUSTOM_COVERAGE_EXCLUDE ".moc$" "moc_" "ui_") diff --git a/ark/HACKING b/ark/HACKING new file mode 100644 index 00000000..27ef2634 --- /dev/null +++ b/ark/HACKING @@ -0,0 +1,52 @@ +== Coding Style == +Ark follows the kdelibs/Qt coding style. For more information about them, +please see: + + - http://techbase.kde.org/Policies/Kdelibs_Coding_Style + - http://wiki.qt-project.org/Coding_Style + - http://wiki.qt-project.org/Coding_Conventions + +== Sending patches == +To send patches for Ark, you can either use git's send-email command and send +it to the kde-utils-devel@kde.org mailing list, or use KDE's ReviewBoard tool +in . In case you choose the latter, the +review should be sent for the Ark product, and the `kdeutils' group should be +added to the review request. + +If you already have a KDE commit account, it is still preferrable to contact +the maintainer instead of committing directly, at least to be a good citizen +and especially so that git mistakes are not made (see the `Using git' section). + +== Using git == +The development model adopted by Ark is simple and rely on git's easy merging +and branching capabilities. If in doubt, do not hesitate to ask! + +First of all, you should do your work in a separate branch, and each commit +should be as atomic as possible. This way, if you are asked to make changes to +them, the rest of your work is not disturbed and you can easily rebase. + +New features are committed to the `master' branch, respecting KDE's Release +Schedule policies. This means the soft and hard freeze periods must be +respected, as well as the string freeze policy. + +Bug fixes are committed to the latest stable branch (for example, KDE/4.8), +which is then merged into the `master' branch. Do *NOT* cherry-pick commits +into multiple branches! It makes following history unnecessarily harder for no +reason. + +To merge the stable branch into `master', the following steps can be followed: + + $ git checkout KDE/4.8 # Whatever the stable branch is + $ # hack, hack, hack + $ # commit + $ git checkout master + $ git merge --log --edit -s recursive -Xours KDE/4.8 + +Do not worry if unrelated commits (such as translation ones made by KDE's +translation infrastructure) are also merged: translation commits are +automatically reverted when needed, and other commits being merged should be +bug fixes by definition. + +When committing your changes, do *NOT* create unnecessary merge commits with +`git pull', as these commits are completely avoidable and make following +history harder. If you are committing your changes, *rebase* first. diff --git a/ark/Mainpage.dox b/ark/Mainpage.dox new file mode 100644 index 00000000..6b9ef1e4 --- /dev/null +++ b/ark/Mainpage.dox @@ -0,0 +1,8 @@ +/** @mainpage ark + +The ark application + +*/ + +// DOXYGEN_REFERENCES = kdecore +// DOXYGEN_SET_PROJECT_NAME = ark diff --git a/ark/Messages.sh b/ark/Messages.sh new file mode 100644 index 00000000..101f99be --- /dev/null +++ b/ark/Messages.sh @@ -0,0 +1,6 @@ +#! /bin/sh +$EXTRACTRC $(find . -name '*.rc') >> rc.cpp || exit 11 +$EXTRACTRC $(find . -name '*.ui') >> rc.cpp || exit 12 +$EXTRACTRC $(find . -name '*.kcfg') >> rc.cpp +$XGETTEXT app/*.cpp kerfuffle/*.cpp part/*.cpp plugins/*/*.cpp rc.cpp -o $podir/ark.pot +rm -f rc.cpp diff --git a/ark/app/CMakeLists.txt b/ark/app/CMakeLists.txt new file mode 100644 index 00000000..f1ef01bc --- /dev/null +++ b/ark/app/CMakeLists.txt @@ -0,0 +1,50 @@ +add_subdirectory(icons) + +set(ark_SRCS + batchextract.cpp + main.cpp + mainwindow.cpp + ) + +# For Mac and Windows. +kde4_add_app_icon(ark_SRCS "${CMAKE_CURRENT_SOURCE_DIR}/icons/hi*-apps-ark.png") + +kde4_add_executable( ark ${ark_SRCS} ) + +target_link_libraries( ark kerfuffle ${KDE4_KFILE_LIBS} ${KDE4_KPARTS_LIBS} ) + +configure_file( + ${CMAKE_CURRENT_SOURCE_DIR}/ark.desktop.cmake + ${CMAKE_CURRENT_BINARY_DIR}/ark.desktop +) + +configure_file( + ${CMAKE_CURRENT_SOURCE_DIR}/ark_dndextract.desktop.cmake + ${CMAKE_CURRENT_BINARY_DIR}/ark_dndextract.desktop +) + +configure_file( + ${CMAKE_CURRENT_SOURCE_DIR}/ark_servicemenu.desktop.cmake + ${CMAKE_CURRENT_BINARY_DIR}/ark_servicemenu.desktop +) + +install( TARGETS ark ${INSTALL_TARGETS_DEFAULT_ARGS} ) +install(FILES ark_addtoservicemenu.desktop ${CMAKE_CURRENT_BINARY_DIR}/ark_servicemenu.desktop DESTINATION ${SERVICES_INSTALL_DIR}/ServiceMenus) +install( PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/ark.desktop DESTINATION ${XDG_APPS_INSTALL_DIR} ) +install( FILES ${CMAKE_CURRENT_SOURCE_DIR}/ark.appdata.xml DESTINATION share/appdata ) +install( FILES arkui.rc DESTINATION ${DATA_INSTALL_DIR}/ark ) + +########### konqueror ark_extract_here plugin ############### +macro_optional_find_package( LibKonq ) +macro_log_feature( LIBKONQ_FOUND "LIBKONQ" "libkonq library" "kdebase" FALSE "" "Need to integrate in konqueror" ) + + +if (LIBKONQ_FOUND) + set(extracthere_SRCS batchextract.cpp extractHereDndPlugin.cpp) + kde4_add_plugin(extracthere WITH_PREFIX ${extracthere_SRCS}) + target_link_libraries(extracthere kerfuffle ${KDE4_KDECORE_LIBS} ${KDE4_KPARTS_LIBS} ${KDE4_KFILE_LIBS} ${LIBKONQ_LIBRARY} ) + include_directories(${LIBKONQ_INCLUDE_DIR}) + install( TARGETS extracthere DESTINATION ${PLUGIN_INSTALL_DIR} ) + install( FILES ${CMAKE_CURRENT_BINARY_DIR}/ark_dndextract.desktop DESTINATION ${SERVICES_INSTALL_DIR} ) +endif (LIBKONQ_FOUND) + diff --git a/ark/app/ark.appdata.xml b/ark/app/ark.appdata.xml new file mode 100644 index 00000000..9de32805 --- /dev/null +++ b/ark/app/ark.appdata.xml @@ -0,0 +1,226 @@ + + + ark.desktop + CC0-1.0 + GPL-2.0+ + Ark + آرك + Ark + Ark + Ark + Ark + Ark + Ark + Ark + Ark + Ark + Ark + Ark + Ark + Ark + Ark + Ark + Ark + Ark + Ark + Ark + Ark + Ark + Ark + Арк + Ark + Арк + Ark + Ark + Ark + Ark + xxArkxx + Ark + 檔案壓縮_Ark + Archiving Tool + أداة أرشفة + Работа с архиви + Eina d'arxivament + Archivační nástroj + Arkiveringsværktøj + Archivprogramm + Archiving Tool + Herramienta de archivado + Arhiivide haldamise rakendus + Pakkausohjelma + Outil d'archivage + Fájltömörítő + Strumento di archiviazione + 압축 도구 + Archyvavimo įrankis + Arkiveringsverktøy + Archievwarktüüch + Archiefgereedschap + Narzędzie do archiwizowania + Ferramenta de Arquivo + Ferramenta de arquivamento + Archivačný nástroj + Orodje za ravnanje z arhivi + Алатка за архивирање + Alatka za arhiviranje + Алатка за архивирање + Alatka za arhiviranje + Arkiveringsverktyg + Arşivleme Aracı + Інструмент роботи з архівами + xxArchiving Toolxx + 压缩工具 + 壓縮工具 + +

+ Ark is a graphical file compression/decompression utility with support for multiple formats, + including tar, gzip, bzip2, rar and zip, as well as CD-ROM images. + Ark can be used to browse, extract, create, and modify archives. +

+

آرك أداة رسوميّة لضغط وفكّ ضغط الملفات مع دعم لصيغ متعدّدة، منها tar، وgzip، وbzip2، وrar، وzip، إضافة إلى صور الأقراص الضوئيّة. يمكن استخدام آرك لتصفح، واستخراج، وإنشاء، وتعديل الأرشيفات.

+

Ark е графичен инструмент за архивиране/разархивиране, поддържащ множество формати, включително tar, gzip, bzip2, rar и zip, както и образи на CD-ROM. Ark може да се използва за разглеждане, извличане, създаване и променяне на архиви.

+

L'Ark és una utilitat gràfica de compressió/descompressió de fitxers que implementa múltiples formats, incloent tar, gzip, bzip2, rar i zip, i també imatges de CD-ROM. L'Ark es pot utilitzar per explorar, extreure, crear, i modificar arxiua.

+

Ark er et grafisk værktøj til komprimering/dekomprimering, med understøttelse for flere formater, derunder tar, gzip, bzip2, rar og zip, såvel som cd-rom-imagefiler. Ark kan bruges til at gennemse, udtrække, oprette og redigere.

+

Ark ist ein grafisches Dienstprogramm zum Packen/Entpacken von Dateien mit Unterstützung für mehrere Formate wie tar, gzip, bzip2, rar, zip und auch CD-Abbilder. Mit Ark können Sie können Sie Archive durchsehen, entpacken, erstellen und bearbeiten.

+

Ark is a graphical file compression/decompression utility with support for multiple formats, including tar, gzip, bzip2, rar and zip, as well as CD-ROM images. Ark can be used to browse, extract, create, and modify archives.

+

Ark es una utilidad gráfica de compresión y descompresión de archivos que permite usar diversos formatos, como tar, gzip, bzip2, rar y zip, así como imágenes de CD-ROM. Ark se puede usar para explorar, extraer, crear y modificar archivos comprimidos.

+

Ark on graafiline failide tihendamise ehk kokkupakkimise ja nende lahtipakkimise tööriist, mis toetab väga paljusid vorminguid, sealhulgas tar, gzip, bzip2, rar and zip, samuti CD-ROM-i tõmmised. Arki abil saab arhiivifaile sirvida, lahti pakkida, luua ja muuta.

+

Ark on graafinen tiedostojen pakkaus-/purkuohjelma, joka tukee useita tiedostomuotoja kuten tar, gzip, bzip2, rar and zip sekä myös CD-ROM-levykuvia. Arkilla voi selata, purkaa, luoda ja muuttaa paketteja.

+

Ark est un utilitaire graphique de compression/décompression de fichier prenant en charge de multiples formats, notamment tar, gzip, bzip2, rar et zip, ainsi que les images de CD-ROM. Ark peut être utilisé pour parcourir, extraire, créer et modifier des archives.

+

Az Ark egy grafikus fájltömörítő és kibontó segédprogram többféle formátum támogatásával, beleértve a tar, gzip, bzip2, rar és zip formátumokat, valamint a CD-ROM képfájlokat. Az Ark használható archívumok böngészéséhez, kibontásához, létrehozásához és módosításához.

+

Ark è uno strumento grafico per la compressione/decompressione dei file che supporta molti formati, tra i quali tar, gzip, bzip2, rar e zip, così come le immagini dei CD-ROM. Ark può essere utilizzato per sfogliare, estrarre, creare e modificare archivi.

+

Ark는 그래픽 압축 파일 관리 도구이며, tar, gzip, bzip2, rar, zip, CD-ROM 이미지를 포함한 여러 파일 형식을 지원합니다. Ark를 사용하여 압축 파일을 열고, 생성하고, 수정하고, 압축을 풀 수 있습니다.

+

Ark er et verktøy med grafisk brukerflate, for komprimering/dekomprimering, med støtte for mange formater, deriblant tar, gzip, rar og zip, samt CD-ROM-bilder. Ark kan brukes til å bla i, pakke opp, opprette og endre arkiver.

+

Ark is en graafsch Komprimeer- un Dekomprimeerwarktüüch, dat en Barg Formaten ünnerstütten deit. Dor sünd tar, gzip, bzip2, rar un zip bi, un ok CD-ROM-Afbiller. Mit Ark kannst Du Archiven dörkieken, opstellen un ännern un Dateien dor ruttrecken.

+

Ark is een grafisch hulpmiddel om bestanden te comprimeren/uit te pakken met ondersteuning van meerdere formaten, inclusief tar, gzip, bzip2, rar en zip, evenals cd-ROM images. Ark kan gebruikt worden om door archieven te bladeren, deze uit te pakken, te maken en te wijzigen.

+

Ark jest graficznym narzędziem do pakowania/rozpakowywania plików wraz z obsługą różnych formatów, włączając w to tar, gzip, bzip2, rar oraz zip, a także obrazy CD-ROM. Programu Ark można używać do przeglądania, tworzenia, zmieniania oraz wydobywania z archiwów

+

O Ark é um utilitário de compressão/descompressão com o suporte para diversos formatos, incluindo o 'tar', 'gzip', 'bzip2', 'rar' e 'zip', assim como imagens de CD-ROM. O Ark pode ser usado para navegar, extrair, criar e modificar esses pacotes.

+

O Ark é um utilitário de compressão/descompressão com o suporte a diversos formatos, incluindo tar, gzip, bzip2, rar e zip, assim como imagens de CD-ROM. O Ark pode ser usado para navegar, extrair, criar e modificar esses arquivos.

+

Ark je grafický nástroj na kompresiu a dekompresiu súborov s podporou pre mnoho formátov, vrátane tar, gzip bzip2, rar a zip, ako ako CD-ROM obrazy. Ark sa dá použiť na prehliadanie, rozbaľovanie vytváranie a úpravu archívov.

+

Ark je grafično orodje za stiskanje/razširjanje datotek, ki podpira večje število arhivov (med podprtimi so tudi tar, gzip, bzip2, rar in zip) kot tudi odtise CD-ROM. Ark lahko uporabite za brskanje po, razširjanje, ustvarjanje in spreminjanje arhivov.

+

Арк је графичка алатка за компресовање и декомпресовање фајлова. Подржава више формата, међу њима и: тар, гзип, бзип2, РАР, ЗИП, као и ЦД одразе. Може се користити за прегледање, распакивање, стварање и мењање архива.

+

Ark je grafička alatka za kompresovanje i dekompresovanje fajlova. Podržava više formata, među njima i: tar, gzip, bzip2, RAR, ZIP, kao i CD odraze. Može se koristiti za pregledanje, raspakivanje, stvaranje i menjanje arhiva.

+

Арк је графичка алатка за компресовање и декомпресовање фајлова. Подржава више формата, међу њима и: тар, гзип, бзип2, РАР, ЗИП, као и ЦД одразе. Може се користити за прегледање, распакивање, стварање и мењање архива.

+

Ark je grafička alatka za kompresovanje i dekompresovanje fajlova. Podržava više formata, među njima i: tar, gzip, bzip2, RAR, ZIP, kao i CD odraze. Može se koristiti za pregledanje, raspakivanje, stvaranje i menjanje arhiva.

+

Ark är ett grafiskt verktyg för komprimering och uppackning av filer med stöd för flera format, inklusive tar, gzip, bzip2, rar och zip, samt cd-rom avbilder. Ark kan användas för att bläddra i, packa upp, skapa och ändra arkiv.

+

Ark; tar, gzip, bzip2, rar ve zip dosyalarının yanında CD-ROM kalıplarını da destekleyen grafiksel bir dosya sıkıştırma/açma yardımcısıdır. Ark arşivlerde gezinmek, arşiv ayıklamak, oluşturmak ve değiştirmek için kullanılabilir.

+

Ark — програма з графічним інтерфейсом, призначена для стискання даних у архіви та видобування даних з архівів. Передбачено підтримку декількох форматів архівів, зокрема tar, gzip, bzip2, rar та zip, а також образів дисків. Ark можна використовувати для перегляду, видобування, створення та внесення змін до архівів.

+

xxArk is a graphical file compression/decompression utility with support for multiple formats, including tar, gzip, bzip2, rar and zip, as well as CD-ROM images. Ark can be used to browse, extract, create, and modify archives.xx

+

Ark 是图形文件压缩和解压工具,支持多种格式,包括 tar, gzip, bzip2, rar and zip 以及 CD-ROM 镜像。Ark 可以用来浏览、解压、创建和修改压缩包。

+

Ark 是一套圖形介面的檔案壓縮/解壓縮工具,支援多種格式,包括 tar, gzip, bzip2, rar 與 zip 等,還有 CD-ROM 映像檔等。Ark 有瀏覽、解開、建立與變更壓縮檔的功能。

+

Features:

+

المزايا

+

Функции:

+

Característiques:

+

Vlastnosti:

+

Funktioner:

+

Leistungsmerkmale:

+

Features:

+

Funcionalidades:

+

Omadused:

+

Ominaisuudet:

+

Fonctionnalités :

+

Szolgáltatások:

+

Funzionalità:

+

기능:

+

Galimybės:

+

Egenskaper:

+

Markmalen:

+

Mogelijkheden:

+

Cechy:

+

Características:

+

Funcionalidades:

+

Funkcie:

+

Zmožnosti:

+

Могућности:

+

Mogućnosti:

+

Могућности:

+

Mogućnosti:

+

Funktioner:

+

Özellikler:

+

Можливості:

+

xxFeatures:xx

+

功能:

+

特色:

+
    +
  • Several formats supported: gzip, bzip2, zip, rar, 7z and more
  • +
  • دعم صيغ متعدّدة: مثل gzip، و bzip2، و zip، و rar، و 7z وغيرها
  • +
  • Поддържани са много формати: gzip, bzip2, zip, rar, 7z и други
  • +
  • Diversos formats implementats: gzip, bzip2, zip, rar, 7z i més
  • +
  • Je podporováno několik formátů: gzip, bzip2, zip, rar, 7z a další
  • +
  • Flere understøttede formater: gzip, bzip2, zip, rar, 7z og flere
  • +
  • Mehrere unterstützte Formate wie gzip, bzip2, zip, rar, 7z und mehr
  • +
  • Several formats supported: gzip, bzip2, zip, rar, 7z and more
  • +
  • Permite el uso de diversos formatos: gzip, bzip2, zip, rar, 7z y más
  • +
  • Paljude vormingute toetus: gzip, bzip2, zip, rar, 7z ja veel paljud.
  • +
  • Tukee useita tiedostomuotoja: gzip, bzip2, zip, rar, 7z ja monia muita
  • +
  • Plusieurs formats sont pris en charge : gzip, bzip2, zip, rar, 7z et d'autres
  • +
  • Számos formátum támogatott: gzip, bzip2, zip, rar, 7z és továbbiak
  • +
  • Diversi formati supportati: gzip, bzip2, zip, rar, 7z e altri
  • +
  • 다양한 형식 지원: gzip, bzip2, zip, rar, 7z 등
  • +
  • Mange formater støttet: gzip, bzip2, zip, rar, 7z og mer
  • +
  • En Reeg Fomaten warrt ünnerstütt: gzip, bzip2, zip, rar, 7z un anner
  • +
  • Ondersteuning voor een aantal formaten: gzip, bzip2, zip, rar, 7z en meer
  • +
  • Obsługa kilku formatów: gzip, bzip2, zip, rar, 7z oraz więcej
  • +
  • Diversos formatos suportados: gzip, bzip2, zip, rar, 7z, entre outros
  • +
  • Diversos formatos suportados: gzip, bzip2, zip, rar, 7z, entre outros
  • +
  • Niektoré z podporovaných formátov: gzip, bzip2, zip, rar, 7z a viac
  • +
  • Številne podprte vrste arhivov: gzip, bzip2, zip, rar, 7z in več
  • +
  • Више подржаних формата: гзип, бзип2, ЗИП, РАР, 7зип, итд.
  • +
  • Više podržanih formata: gzip, bzip2, ZIP, RAR, 7zip, itd.
  • +
  • Више подржаних формата: гзип, бзип2, ЗИП, РАР, 7зип, итд.
  • +
  • Više podržanih formata: gzip, bzip2, ZIP, RAR, 7zip, itd.
  • +
  • En mängd format stöds: gzip, bzip2, zip, rar, 7z med flera
  • +
  • Çeşitli biçimler destekleniyor: gzip, bzip2, zip, rar, 7z ve dahası
  • +
  • Підтримка декількох форматів: gzip, bzip2, zip, rar, 7z тощо
  • +
  • xxSeveral formats supported: gzip, bzip2, zip, rar, 7z and morexx
  • +
  • 支持多种格式:gzip, bzip2, zip, rar, 7z 等
  • +
  • 支援格式:gzip, bzip2, zip, rar, 7z 等等
  • +
  • Preview file contents without extracting files
  • +
  • استعراض محتوى الملف دون استخراج الملفات منه
  • +
  • Преглеждане съдържанието на файла, без извличане на файловете
  • +
  • Vista prèvia del contingut de fitxers sense extreure'ls
  • +
  • Náhled obsahu souborů bez jejich rozbalení
  • +
  • Forhåndsvis filindhold uden at udtrække filer
  • +
  • Vorschau der Dateiinhalte ohne das Entpacken der Dateien
  • +
  • Preview file contents without extracting files
  • +
  • Vista previa del contenido de archivos sin extraerlos
  • +
  • Failide sisu näitamine ilma neid lahti pakkimata.
  • +
  • Tiedoston sisällön esikatselu purkamatta tiedostoa
  • +
  • Prévisualiser le contenu des fichiers sans les extraire
  • +
  • Fájltartalom előnézete a fájlok kibontása nélkül
  • +
  • Anteprima dei file senza estrarre i file
  • +
  • 압축 풀지 않고 파일 내용 미리 보기
  • +
  • Forhåndsvis innhold uten å pakke ut filer
  • +
  • Datein ahn Ruttrecken vörweg ankieken
  • +
  • Voorvertoning van inhoud van bestand zonder bestanden uit te pakken
  • +
  • Podejrzyj zawartość pliku bez jego wypakowywania
  • +
  • Antevisão do conteúdo dos ficheiros sem os extrair
  • +
  • Visualização do conteúdo dos arquivos sem precisar extraí-los
  • +
  • Náhľad obsahu súboru bez rozbalenia súborov
  • +
  • Predoglejte vsebino datoteke brez razširjanja
  • +
  • Преглед садржаја фајлова без распакивања.
  • +
  • Pregled sadržaja fajlova bez raspakivanja.
  • +
  • Преглед садржаја фајлова без распакивања.
  • +
  • Pregled sadržaja fajlova bez raspakivanja.
  • +
  • Förhandsgranska filinnehåll utan att packa upp filer
  • +
  • Dosyaları ayıklamadan dosya içeriği önizlemesi
  • +
  • Попередній перегляд вмісту файлів без розпаковування
  • +
  • xxPreview file contents without extracting filesxx
  • +
  • 不解压就预览文件
  • +
  • 不需解壓縮即可預覽檔案內容
  • +
+
+ + + http://kde.org/images/screenshots/ark.png + + + http://kde.org/applications/utilities/ark/ + https://bugs.kde.org/enter_bug.cgi?format=guided&product=ark + http://docs.kde.org/stable/en/kdeutils/ark/index.html + KDE + + ark + +
diff --git a/ark/app/ark.desktop.cmake b/ark/app/ark.desktop.cmake new file mode 100755 index 00000000..9f0daeaf --- /dev/null +++ b/ark/app/ark.desktop.cmake @@ -0,0 +1,158 @@ +[Desktop Entry] +MimeType=@SUPPORTED_ARK_MIMETYPES@ +GenericName=Archiving Tool +GenericName[af]=Argiveer Program +GenericName[ar]=أداة أرشفة +GenericName[ast]=Ferramienta p'archivar +GenericName[bg]=Работа с архиви +GenericName[br]=Ostilh merañ an Dielloù +GenericName[bs]=Alatka za arhiviranje +GenericName[ca]=Eina d'arxivament +GenericName[ca@valencia]=Eina d'arxivament +GenericName[cs]=Archivační nástroj +GenericName[cy]=Erfyn Archifo +GenericName[da]=Arkiveringsværktøj +GenericName[de]=Archivprogramm +GenericName[el]=Εργαλείο αρχειοθέτησης +GenericName[en_GB]=Archiving Tool +GenericName[eo]=Arkivilo +GenericName[es]=Archivador +GenericName[et]=Arhiivide haldamise rakendus +GenericName[eu]=Artxibatzeko tresna +GenericName[fa]=ابزار بایگانی +GenericName[fi]=Pakkausohjelma +GenericName[fr]=Outil d'archivage +GenericName[ga]=Uirlis Chartlannaithe +GenericName[gl]=Utilidade de arquivo +GenericName[he]=כלי לניהול ארכיונים +GenericName[hne]=अभिलेखन औजार +GenericName[hr]=Alat za arhiviranje +GenericName[hu]=Fájltömörítő +GenericName[ia]=Instrumento per archivar +GenericName[id]=Perkakas Pengarsip +GenericName[is]=Vinna með safnskrár +GenericName[it]=Strumento di archiviazione +GenericName[ja]=アーカイブツール +GenericName[kk]=Архивтеу құралы +GenericName[km]=ឧបករណ៍​ប័ណ្ណសារ +GenericName[ko]=압축 도구 +GenericName[lt]=Archyvavimo priemonė +GenericName[lv]=Arhivēšanas rīks +GenericName[mk]=Алатка за архивирање +GenericName[mr]=संग्रह साधन +GenericName[ms]=Alatan Pengarkiban +GenericName[nb]=Arkiveringsverktøy +GenericName[nds]=Archievwarktüüch +GenericName[ne]=सङ्ग्रहण उपकरण +GenericName[nl]=Archiefgereedschap +GenericName[nn]=Arkiveringsverktøy +GenericName[pa]=ਅਕਾਇਵਿੰਗ ਟੂਲ +GenericName[pl]=Narzędzie do archiwizacji +GenericName[pt]=Ferramenta de Armazenamento +GenericName[pt_BR]=Ferramenta de Arquivamento +GenericName[ro]=Utilitar de arhivare +GenericName[ru]=Архиватор +GenericName[sk]=Archivačný nástroj +GenericName[sl]=Orodje za ravnanje z arhivi +GenericName[sq]=Mjeti Arkivues +GenericName[sr]=Алатка за архивирање +GenericName[sr@ijekavian]=Алатка за архивирање +GenericName[sr@ijekavianlatin]=Alatka za arhiviranje +GenericName[sr@latin]=Alatka za arhiviranje +GenericName[sv]=Arkiveringsverktyg +GenericName[ta]=காப்பக கருவி +GenericName[tg]=Асбобҳои Бойгонӣ +GenericName[th]=เครื่องมือจัดการแฟ้มจัดเก็บ +GenericName[tr]=Arşivleme Aracı +GenericName[ug]=ئارخىپ قورالى +GenericName[uk]=Засіб роботи з архівами +GenericName[uz]=Arxivlash vositasi +GenericName[uz@cyrillic]=Архивлаш воситаси +GenericName[vi]=Công Cụ Nén +GenericName[wa]=Usteye d' årtchivaedje +GenericName[xh]=Isixhobo Sokuphatha i Archive +GenericName[x-test]=xxArchiving Toolxx +GenericName[zh_CN]=压缩归档工具 +GenericName[zh_TW]=壓縮工具 +Name=Ark +Name[af]=Ark +Name[ar]=أرك +Name[ast]=Ark +Name[bg]=Ark +Name[br]=Ark +Name[bs]=Ark +Name[ca]=Ark +Name[ca@valencia]=Ark +Name[cs]=Ark +Name[cy]=Ark +Name[da]=Ark +Name[de]=Ark +Name[el]=Ark +Name[en_GB]=Ark +Name[eo]=Arko +Name[es]=Ark +Name[et]=Ark +Name[eu]=Ark +Name[fi]=Ark +Name[fr]=Ark +Name[ga]=Ark +Name[gl]=Ark +Name[he]=Ark +Name[hne]=आर्क +Name[hr]=Ark +Name[hu]=Ark +Name[ia]=Ark +Name[id]=Ark +Name[is]=Ark +Name[it]=Ark +Name[ja]=Ark +Name[kk]=Ark +Name[km]=Ark +Name[ko]=Ark +Name[lt]=Ark +Name[lv]=Ark +Name[mk]=Ark +Name[mr]=आर्क +Name[ms]=Ark +Name[nb]=Ark +Name[nds]=Ark +Name[ne]=आर्क +Name[nl]=Ark +Name[nn]=Ark +Name[pa]=ਆਕ +Name[pl]=Ark +Name[pt]=Ark +Name[pt_BR]=Ark +Name[ro]=Ark +Name[ru]=Ark +Name[sk]=Ark +Name[sl]=Ark +Name[sq]=Ark +Name[sr]=Арк +Name[sr@ijekavian]=Арк +Name[sr@ijekavianlatin]=Ark +Name[sr@latin]=Ark +Name[sv]=Ark +Name[ta]=ஆர்க் +Name[tg]=Ark +Name[th]=อาร์ก +Name[tr]=Ark +Name[ug]=Ark +Name[uk]=Ark +Name[uz]=Ark +Name[uz@cyrillic]=Ark +Name[vi]=Ark +Name[wa]=Ark +Name[xh]=Ark +Name[x-test]=xxArkxx +Name[zh_CN]=Ark +Name[zh_TW]=Ark +Exec=ark -caption %c %U +Icon=ark +X-DocPath=ark/index.html +Type=Application +Terminal=false +X-DBUS-StartupType=Unique +X-KDE-HasTempFileOption=true +Categories=Qt;KDE;Utility;Archiving;Compression;X-KDE-Utilities-File; +InitialPreference=3 diff --git a/ark/app/ark_addtoservicemenu.desktop b/ark/app/ark_addtoservicemenu.desktop new file mode 100644 index 00000000..73cb805d --- /dev/null +++ b/ark/app/ark_addtoservicemenu.desktop @@ -0,0 +1,351 @@ +[Desktop Entry] +Type=Service +ServiceTypes=KonqPopupMenu/Plugin +MimeType=all/all; +Actions=compressHere;compressAsZip;compressAsRar;compressAsTar;compressTo; +X-KDE-Submenu=Compress +X-KDE-Submenu[ar]=اضغط +X-KDE-Submenu[ast]=Comprimir +X-KDE-Submenu[bg]=Компресиране +X-KDE-Submenu[bs]=Kompresuj +X-KDE-Submenu[ca]=Compressió +X-KDE-Submenu[ca@valencia]=Compressió +X-KDE-Submenu[cs]=Zkomprimovat +X-KDE-Submenu[da]=Komprimér +X-KDE-Submenu[de]=Komprimieren +X-KDE-Submenu[el]=Συμπίεση +X-KDE-Submenu[en_GB]=Compress +X-KDE-Submenu[es]=Comprimir +X-KDE-Submenu[et]=Paki +X-KDE-Submenu[eu]=Konprimatu +X-KDE-Submenu[fi]=Pakkaa +X-KDE-Submenu[fr]=Compresser +X-KDE-Submenu[ga]=Comhbhrúigh +X-KDE-Submenu[gl]=Comprimir +X-KDE-Submenu[hne]=संपीडित करव +X-KDE-Submenu[hr]=Zapakiraj +X-KDE-Submenu[hu]=Tömörítés +X-KDE-Submenu[ia]=Comprime +X-KDE-Submenu[id]=Kompres +X-KDE-Submenu[it]=Comprimi +X-KDE-Submenu[ja]=圧縮 +X-KDE-Submenu[kk]=Сығу +X-KDE-Submenu[km]=បង្ហាប់ +X-KDE-Submenu[ko]=압축하기 +X-KDE-Submenu[lt]=Suspausti +X-KDE-Submenu[lv]=Saspiest +X-KDE-Submenu[mr]=संक्षिप्त करा +X-KDE-Submenu[nb]=Komprimer +X-KDE-Submenu[nds]=Komprimeren +X-KDE-Submenu[nl]=Comprimeren +X-KDE-Submenu[nn]=Komprimer +X-KDE-Submenu[pa]=ਕੰਪਰੈੱਸ +X-KDE-Submenu[pl]=Kompresuj +X-KDE-Submenu[pt]=Comprimir +X-KDE-Submenu[pt_BR]=Compactar +X-KDE-Submenu[ro]=Comprimă +X-KDE-Submenu[ru]=Упаковать +X-KDE-Submenu[sk]=Komprimovať +X-KDE-Submenu[sl]=Stisni +X-KDE-Submenu[sq]=Ngjish +X-KDE-Submenu[sr]=Компресуј +X-KDE-Submenu[sr@ijekavian]=Компресуј +X-KDE-Submenu[sr@ijekavianlatin]=Kompresuj +X-KDE-Submenu[sr@latin]=Kompresuj +X-KDE-Submenu[sv]=Komprimera +X-KDE-Submenu[th]=บีบข้อมูล +X-KDE-Submenu[tr]=Sıkıştır +X-KDE-Submenu[ug]=پرېس +X-KDE-Submenu[uk]=Стиснути +X-KDE-Submenu[wa]=Rastrinde +X-KDE-Submenu[x-test]=xxCompressxx +X-KDE-Submenu[zh_CN]=压缩 +X-KDE-Submenu[zh_TW]=壓縮 +X-KDE-StartupNotify=false +X-KDE-Priority=TopLevel + +[Desktop Action compressHere] +Name=Here +Name[ar]=هنا +Name[ast]=Equí +Name[bg]=Тук +Name[bs]=ovdje +Name[ca]=Aquí +Name[ca@valencia]=Ací +Name[cs]=Sem +Name[da]=Her +Name[de]=Hier +Name[el]=Εδώ +Name[en_GB]=Here +Name[es]=Aquí +Name[et]=Siia +Name[eu]=Hemen +Name[fi]=Tähän +Name[fr]=Ici +Name[ga]=Anseo +Name[gl]=Aquí +Name[hr]=Ovdje +Name[hu]=Ide +Name[ia]=Hic +Name[id]=Di Sini +Name[is]=Hér +Name[it]=Qui +Name[ja]=ここに +Name[kk]=Осында +Name[km]=នៅ​ទីនេះ +Name[ko]=여기 +Name[lt]=Čia +Name[lv]=Šeit +Name[mr]=येथे +Name[nb]=Her +Name[nds]=Hier +Name[nl]=Hier +Name[nn]=Her +Name[pa]=ਇੱਥੇ +Name[pl]=Tutaj +Name[pt]=Aqui +Name[pt_BR]=Aqui +Name[ro]=Aici +Name[ru]=В эту папку +Name[sk]=Sem +Name[sl]=Sem +Name[sq]=Këtu +Name[sr]=овде +Name[sr@ijekavian]=овдје +Name[sr@ijekavianlatin]=ovdje +Name[sr@latin]=ovde +Name[sv]=Här +Name[th]=ไว้ที่นี่ +Name[tr]=Buraya +Name[ug]=بۇ جاي +Name[uk]=Сюди +Name[wa]=Cial +Name[x-test]=xxHerexx +Name[zh_CN]=这里 +Name[zh_TW]=這裡 +Icon=ark +Exec=ark --changetofirstpath --add --autofilename tar.gz %F + +[Desktop Action compressAsZip] +Name=As ZIP Archive +Name[ar]=ك أرشيف ZIP +Name[ast]=Como archivu comprimíu ZIP +Name[bg]=Като ZIP архив +Name[bs]=kao ZIP arhivu +Name[ca]=Com a arxiu ZIP +Name[ca@valencia]=Com a arxiu ZIP +Name[cs]=Jako archiv ZIP +Name[da]=Som ZIP-arkiv +Name[de]=Als ZIP-Archiv +Name[el]=Ως αρχειοθήκη ZIP +Name[en_GB]=As ZIP Archive +Name[es]=Como archivo comprimido ZIP +Name[et]=ZIP-arhiivina +Name[eu]=ZIP artxibo gisa +Name[fi]=ZIP-paketiksi +Name[fr]=Comme une archive « ZIP » +Name[ga]=Mar Chartlann ZIP +Name[gl]=Como arquivo ZIP +Name[hr]=Kao ZIP-arhiva +Name[hu]=ZIP archívumként +Name[ia]=Como archivo ZIP +Name[id]=Sebagai Arsip ZIP +Name[it]=Come archivio ZIP +Name[ja]=ZIP アーカイブに +Name[kk]=ZIP архиві қылып +Name[km]=ជា​ប័ណ្ណសារ ZIP +Name[ko]=ZIP 파일로 +Name[lt]=ZIP archyvas +Name[lv]=Kā ZIP arhīvu +Name[mr]=ZIP संग्रह प्रमाणे +Name[nb]=Som ZIP-arkiv +Name[nds]=As Zip-Archiev +Name[nl]=Als ZIP-archief +Name[nn]=Som ZIP-arkiv +Name[pa]=ਜ਼ਿਪ ਅਕਾਇਵ ਵਾਂਗ +Name[pl]=Jako archiwum ZIP +Name[pt]=Como Pacote ZIP +Name[pt_BR]=Como arquivo ZIP +Name[ro]=Ca arhivă ZIP +Name[ru]=Как архив ZIP +Name[sk]=Do ZIP archívu +Name[sl]=Kot arhiv ZIP +Name[sq]=Si Arkiv ZIP +Name[sr]=као ЗИП архиву +Name[sr@ijekavian]=као ЗИП архиву +Name[sr@ijekavianlatin]=kao ZIP arhivu +Name[sr@latin]=kao ZIP arhivu +Name[sv]=Som ZIP-arkiv +Name[th]=เป็นแฟ้มจัดเก็บบีบอัดแบบ ZIP +Name[tr]=ZIP Arşivi Olarak +Name[ug]=ZIP ئارخىپى +Name[uk]=Як архів ZIP +Name[wa]=Come årtchive ZIP +Name[x-test]=xxAs ZIP Archivexx +Name[zh_CN]=为 ZIP 归档 +Name[zh_TW]=成 ZIP 壓縮檔 +Icon=ark +Exec=ark --changetofirstpath --add --autofilename zip %F + +[Desktop Action compressAsRar] +Name=As RAR Archive +Name[ar]=كأرشيف RAR +Name[ast]=Como archivu comprimíu RAR +Name[bg]=Като RAR архив +Name[bs]=kao RAR arhivu +Name[ca]=Com a arxiu RAR +Name[ca@valencia]=Com a arxiu RAR +Name[cs]=Jako archiv RAR +Name[da]=Som RAR-arkiv +Name[de]=Als RAR-Archiv +Name[el]=Ως αρχειοθήκη RAR +Name[en_GB]=As RAR Archive +Name[es]=Como archivo comprimido RAR +Name[et]=RAR-arhiivina +Name[eu]=RAR artxibo gisa +Name[fi]=RAR-paketiksi +Name[fr]=Comme une archive « RAR » +Name[ga]=Mar Chartlann RAR +Name[gl]=Como arquivo RAR +Name[hr]=Kao RAR-arhiva +Name[hu]=RAR archívumként +Name[ia]=Como archivo RAR +Name[id]=Sebagai Arsip RAR +Name[it]=Come archivio RAR +Name[ja]=RAR アーカイブに +Name[kk]=RAR архиві қылып +Name[km]=​ជា​ប័ណ្ណសារ RAR +Name[ko]=RAR 파일로 +Name[lt]=RAR archyvas +Name[lv]=Kā RAR arhīvu +Name[mr]=RAR संग्रह प्रमाणे +Name[nb]=Som RAR-arkiv +Name[nds]=As RAR-Archiev +Name[nl]=Als RAR-archief +Name[nn]=Som RAR-arkiv +Name[pa]=RAR ਅਕਾਇਵ ਵਾਂਗ +Name[pl]=Jako archiwum RAR +Name[pt]=Como Pacote RAR +Name[pt_BR]=Como arquivo RAR +Name[ro]=Ca arhivă RAR +Name[ru]=Как архив RAR +Name[sk]=Do RAR archívu +Name[sl]=Kot arhiv RAR +Name[sq]=Si Arkiv RAR +Name[sr]=као РАР архиву +Name[sr@ijekavian]=као РАР архиву +Name[sr@ijekavianlatin]=kao RAR arhivu +Name[sr@latin]=kao RAR arhivu +Name[sv]=Som RAR-arkiv +Name[th]=เป็นแฟ้มจัดเก็บบีบอัดแบบ RAR +Name[tr]=RAR Arşivi Olarak +Name[ug]=RAR ئارخىپى +Name[uk]=Як архів RAR +Name[wa]=Come årtchive RAR +Name[x-test]=xxAs RAR Archivexx +Name[zh_CN]=为 RAR 归档 +Name[zh_TW]=成 RAR 壓縮檔 +Icon=ark +Exec=ark --changetofirstpath --add --autofilename rar %F + +[Desktop Action compressAsTar] +Name=As TAR.GZ Archive +Name[ar]=كأرشيف TAR.GZ +Name[bg]=Като TAR.GZ архив +Name[ca]=Com a arxiu TAR.GZ +Name[cs]=Jako archiv TAR.GZ +Name[da]=Som TAR.GZ-arkiv +Name[de]=Als TAR.GZ-Archiv +Name[en_GB]=As TAR.GZ Archive +Name[es]=Como archivo comprimido TAR.GZ +Name[et]=TAR.GZ-arhiivina +Name[fi]=TAR.GZ-paketiksi +Name[fr]=Comme une archive « TAR.GZ » +Name[hu]=TAR.GZ archívumként +Name[ia]=Como archivo TAR.GZ +Name[it]=Come archivio TAR.GZ +Name[ja]=TAR.GZ アーカイブに +Name[ko]=TAR.GZ 파일로 +Name[nb]=Som TAR.GZ-arkiv +Name[nds]=As TAR.GZ-Archiev +Name[nl]=Als TAR.GZ-archief +Name[pa]=TAR.GZ ਅਕਾਇਵ ਵਜੋਂ +Name[pl]=Jako archiwum TAR.GZ +Name[pt]=Como Pacote TAR.GZ +Name[pt_BR]=Como arquivo TAR.GZ +Name[ru]=Как архив TAR.GZ +Name[sk]=Ako TAR.GZ archív +Name[sl]=Kot arhiv TAR.GZ +Name[sr]=као таргз архиву +Name[sr@ijekavian]=као таргз архиву +Name[sr@ijekavianlatin]=kao targz arhivu +Name[sr@latin]=kao targz arhivu +Name[sv]=Som TAR.GZ-arkiv +Name[tr]=TAR.GZ Arşivi Olarak +Name[uk]=Як архів TAR.GZ +Name[x-test]=xxAs TAR.GZ Archivexx +Name[zh_CN]=为 TAR.GZ 归档 +Name[zh_TW]=成 TAR.GZ 壓縮檔 +Icon=ark +Exec=ark --changetofirstpath --add --autofilename tar.gz %F + +[Desktop Action compressTo] +Name=Compress To... +Name[ar]=اضغط إلى... +Name[ast]=Comprimir en... +Name[bg]=Компресиране в... +Name[bs]=Kompresuj u... +Name[ca]=Comprimeix a... +Name[ca@valencia]=Comprimeix a... +Name[cs]=Zkomprimovat do... +Name[da]=Komprimér til... +Name[de]=Komprimieren nach ... +Name[el]=Συμπίεση σε... +Name[en_GB]=Compress To... +Name[es]=Comprimir en... +Name[et]=Paki asukohta... +Name[eu]=Konprimatu hona... +Name[fi]=Pakkaa… +Name[fr]=Compresser vers... +Name[ga]=Comhbhrúigh Go... +Name[gl]=Comprimir en... +Name[hr]=Zapakiraj u … +Name[hu]=Tömörítés ide… +Name[ia]=Comprime a... +Name[id]=Kompres ke... +Name[it]=Comprimi in... +Name[ja]=圧縮... +Name[kk]=Мыныған архивтеу,,, +Name[km]=បង្ហាប់​ទៅ​ជា​... +Name[ko]=다음으로 압축하기... +Name[lt]=Suspausti į... +Name[lv]=Saspiest uz... +Name[mr]=यावर संक्षिप्त करा... +Name[nb]=Komprimer til … +Name[nds]=Komprimeren as... +Name[nl]=Comprimeren naar... +Name[nn]=Komprimer til … +Name[pa]=...ਵਜੋਂ ਕੰਪਰੈੱਸ ਕਰੋ +Name[pl]=Kompresuj do... +Name[pt]=Comprimir Para... +Name[pt_BR]=Compactar para... +Name[ro]=Comprimă în... +Name[ru]=Упаковать в архив... +Name[sk]=Komprimovať do... +Name[sl]=Stisni v ... +Name[sq]=Ngjishe Tek... +Name[sr]=другде... +Name[sr@ijekavian]=другдје... +Name[sr@ijekavianlatin]=drugdje... +Name[sr@latin]=drugde... +Name[sv]=Komprimera till... +Name[th]=บีบข้อมูลไปยัง... +Name[tr]=Farklı Sıkıştır... +Name[ug]=… غا پرېسلاش +Name[uk]=Стиснути до... +Name[wa]=Rastrinde eyet rlomer... +Name[x-test]=xxCompress To...xx +Name[zh_CN]=压缩到... +Name[zh_TW]=壓縮為... +Icon=ark +Exec=ark --add --changetofirstpath --dialog %F diff --git a/ark/app/ark_dndextract.desktop.cmake b/ark/app/ark_dndextract.desktop.cmake new file mode 100644 index 00000000..f1ac26a4 --- /dev/null +++ b/ark/app/ark_dndextract.desktop.cmake @@ -0,0 +1,61 @@ +[Desktop Entry] +Type=Service +X-KDE-ServiceTypes=KonqDndPopupMenu/Plugin +X-KDE-Library=libextracthere +Name=Ark Extract Here +Name[ar]=أرك استخرج هنا +Name[ast]=Estrayer equí con Ark +Name[bg]=Извличане тук +Name[bs]=Raspakuj Arkom ovde +Name[ca]=Extracció de l'Ark aquí +Name[ca@valencia]=Extracció de l'Ark ací +Name[cs]=Rozbalit Arkem sem +Name[da]=Pak ud med Ark her +Name[de]=Ark – Hierher auspacken +Name[el]=Εξαγωγή Ark εδώ +Name[en_GB]=Ark Extract Here +Name[es]=Extraer aquí con Ark +Name[et]=Paki siia lahti +Name[eu]=Ark: atera hemen +Name[fi]=Ark – Pura tähän +Name[fr]=Extraire un fichier « Ark » ici +Name[ga]=Bain an chartlann Ark amach agus cuir é anseo +Name[gl]=Extraer aquí con Ark +Name[hr]=Ovdje Ark-otpakiraj +Name[hu]=Kibontás ide +Name[ia]=Ark Extrahe hic +Name[id]=Ark Ekstrak di Sini +Name[it]=Ark Estrai qui +Name[ja]=Ark ここに展開 +Name[kk]=Мынаған тарқату +Name[km]=ស្រង់ចេញ Ark នៅ​ទីនេះ +Name[ko]=Ark 여기에 풀기 +Name[lt]=Čia išpakuoti archyvą +Name[lv]=Ark atspiest šeit +Name[mr]=येथे आर्क पुर्ववत करा +Name[nb]=Pakk ut arkivet her med Ark +Name[nds]=Hier mit Ark utpacken +Name[nl]=Ark: hier uitpakken +Name[nn]=Pakk ut med Ark her +Name[pa]=ਅਕਾਇਵ ਇੱਥੇ ਖੋਲ੍ਹੋ +Name[pl]=Rozpakuj tutaj +Name[pt]=Extrair para Aqui +Name[pt_BR]=Extrair com o Ark aqui +Name[ro]=Ark extrage aici +Name[ru]=Распаковать в эту папку +Name[sk]=Ark - rozbaliť sem +Name[sl]=Razširi sem z Arkom +Name[sq]=Ark Ekstrakton Këtu +Name[sr]=Распакуј Арком овде +Name[sr@ijekavian]=Распакуј Арком овдје +Name[sr@ijekavianlatin]=Raspakuj Arkom ovdje +Name[sr@latin]=Raspakuj Arkom ovde +Name[sv]=Packa upp arkiv här +Name[th]=อาร์ก คลายแฟ้มไว้ที่นี่ +Name[tr]=Ark Arşivi Buraya Çıkart +Name[uk]=Видобути архів сюди з Ark +Name[wa]=Saetchî foû avou Ark cial +Name[x-test]=xxArk Extract Herexx +Name[zh_CN]=Ark 在此解压缩 +Name[zh_TW]=在此解壓縮 +MimeType=@SUPPORTED_ARK_MIMETYPES@ diff --git a/ark/app/ark_servicemenu.desktop.cmake b/ark/app/ark_servicemenu.desktop.cmake new file mode 100644 index 00000000..41772bdf --- /dev/null +++ b/ark/app/ark_servicemenu.desktop.cmake @@ -0,0 +1,238 @@ +[Desktop Entry] +Type=Service +ServiceTypes=KonqPopupMenu/Plugin +MimeType=@SUPPORTED_ARK_MIMETYPES@ +Actions=arkAutoExtractHere;arkExtractTo;arkExtractHere; +X-KDE-Priority=TopLevel +X-KDE-StartupNotify=false +#StartupNotify=false +X-KDE-Submenu=Extract +X-KDE-Submenu[ar]=استخرِج +X-KDE-Submenu[bg]=Извличане +X-KDE-Submenu[bs]=Raspakivanje +X-KDE-Submenu[ca]=Extreu +X-KDE-Submenu[ca@valencia]=Extrau +X-KDE-Submenu[cs]=Rozbalit +X-KDE-Submenu[da]=Pak ud +X-KDE-Submenu[de]=Entpacken +X-KDE-Submenu[el]=Εξαγωγή +X-KDE-Submenu[en_GB]=Extract +X-KDE-Submenu[es]=Extraer +X-KDE-Submenu[et]=Lahtipakkimine +X-KDE-Submenu[eu]=Atera +X-KDE-Submenu[fi]=Pura +X-KDE-Submenu[fr]=Extraire +X-KDE-Submenu[ga]=Bain amach +X-KDE-Submenu[gl]=Extraer +X-KDE-Submenu[hr]=Otpakiraj +X-KDE-Submenu[hu]=Kibontás +X-KDE-Submenu[ia]=Extrahe +X-KDE-Submenu[it]=Estrai +X-KDE-Submenu[ja]=展開 +X-KDE-Submenu[kk]=Тарқату +X-KDE-Submenu[km]=ស្រង់ចេញ​ +X-KDE-Submenu[ko]=압축 풀기 +X-KDE-Submenu[lt]=Išpakuoti +X-KDE-Submenu[mr]=पुर्ववत करा +X-KDE-Submenu[nb]=Pakk ut +X-KDE-Submenu[nds]=Utpacken +X-KDE-Submenu[nl]=Uitpakken +X-KDE-Submenu[pa]=ਇੱਥੇ ਖਿਲਾਰੋ +X-KDE-Submenu[pl]=Rozpakuj +X-KDE-Submenu[pt]=Extrair +X-KDE-Submenu[pt_BR]=Extrair +X-KDE-Submenu[ro]=Extrage +X-KDE-Submenu[ru]=Распаковать +X-KDE-Submenu[sk]=Rozbaliť +X-KDE-Submenu[sl]=Razširi +X-KDE-Submenu[sr]=Распакуј +X-KDE-Submenu[sr@ijekavian]=Распакуј +X-KDE-Submenu[sr@ijekavianlatin]=Raspakuj +X-KDE-Submenu[sr@latin]=Raspakuj +X-KDE-Submenu[sv]=Packa upp +X-KDE-Submenu[tr]=Çıkart +X-KDE-Submenu[ug]=ئايرىش +X-KDE-Submenu[uk]=Видобути +X-KDE-Submenu[x-test]=xxExtractxx +X-KDE-Submenu[zh_CN]=解压缩 +X-KDE-Submenu[zh_TW]=解開 + +[Desktop Action arkExtractHere] +Name=Extract Archive Here +Name[ar]=فك الأرشيف هنا +Name[ast]=Estrayer archivu comprimíu equí +Name[bg]=Извличане тук +Name[bs]=Raspakuj arhivu ovde +Name[ca]=Extreu l'arxiu aquí +Name[ca@valencia]=Extrau l'arxiu ací +Name[cs]=Rozbalit archiv sem +Name[da]=Udpak arkiv her +Name[de]=Archiv hierher auspacken +Name[el]=Εξαγωγή αρχειοθήκης εδώ +Name[en_GB]=Extract Archive Here +Name[es]=Extraer archivo comprimido aquí +Name[et]=Paki arhiiv siia lahti +Name[eu]=Atera artxiboa hemen +Name[fi]=Pura paketti tähän +Name[fr]=Extraire l'archive ici +Name[ga]=Bain an chartlann amach agus cuir é anseo +Name[gl]=Extraer o arquivo aquí +Name[hr]=Ovdje otpakiraj arhivu +Name[hu]=Kibontás ide +Name[ia]=Extrahe archivo hic +Name[id]=Ekstrak Arsip di Sini +Name[it]=Estrai l'archivio qui +Name[ja]=アーカイブをここに展開 +Name[kk]=Архивті мынаған тарқату +Name[km]=ស្រង់ចេញ​ប័ណ្ណសារ​នៅ​ទីនេះ +Name[ko]=여기에 압축 풀기 +Name[lt]=Čia išpakuoti archyvą +Name[lv]=Atspiest arhīvu šeit +Name[mr]=येथे संग्रह पुर्ववत करा +Name[nb]=Pakk ut arkivet her +Name[nds]=Archiev hier utpacken +Name[nl]=Archief hier uitpakken +Name[nn]=Pakk ut arkivet her +Name[pa]=ਅਕਾਇਵ ਇੱਥੇ ਖੋਲ੍ਹੋ +Name[pl]=Rozpakuj archiwum tutaj +Name[pt]=Extrair o Pacote para Aqui +Name[pt_BR]=Extrair arquivo aqui +Name[ro]=Extrage arhiva aici +Name[ru]=В эту папку +Name[sk]=Rozbaliť archív sem +Name[sl]=Razširi arhiv sem +Name[sq]=Ekstrakto Arkivin Këtu +Name[sr]=Распакуј архиву овде +Name[sr@ijekavian]=Распакуј архиву овдје +Name[sr@ijekavianlatin]=Raspakuj arhivu ovdje +Name[sr@latin]=Raspakuj arhivu ovde +Name[sv]=Packa upp arkiv här +Name[th]=คลายแฟ้มจัดเก็บไว้ที่นี่ +Name[tr]=Arşivi Buraya Çıkart +Name[uk]=Видобути архів сюди +Name[wa]=Saetchî l' årtchive foû cial +Name[x-test]=xxExtract Archive Herexx +Name[zh_CN]=在此解压缩归档 +Name[zh_TW]=在此解壓縮 +Icon=ark +Exec=ark --batch --autodestination %F + +[Desktop Action arkExtractTo] +Name=Extract Archive To... +Name[ar]=فك الأرشيف إلى... +Name[ast]=Estrayer archivu comprimíu en... +Name[bg]=Извличане в... +Name[bs]=Raspakuj arhivu u... +Name[ca]=Extreu l'arxiu a... +Name[ca@valencia]=Extrau l'arxiu a... +Name[cs]=Rozbalit archiv do... +Name[da]=Udpak arkiv til... +Name[de]=Archiv auspacken nach ... +Name[el]=Εξαγωγή αρχειοθήκης σε... +Name[en_GB]=Extract Archive To... +Name[es]=Extraer archivo comprimido en... +Name[et]=Paki arhiiv lahti... +Name[eu]=Atera artxiboa hona... +Name[fi]=Pura paketti… +Name[fr]=Extraire l'archive vers... +Name[ga]=Bain an chartlann amach go... +Name[gl]=Extraer o arquivo en... +Name[hr]=Otpakiraj arhivu u … +Name[hu]=Kibontás ide… +Name[ia]=Extrahe archivo in... +Name[id]=Ekstrak Arsip ke... +Name[it]=Estrai l'archivio in... +Name[ja]=アーカイブを展開... +Name[kk]=Мынаған тарқату... +Name[km]=ស្រង់ចេញ​ប័ណ្ណសារ​ទៅ... +Name[ko]=다음 경로에 압축 풀기... +Name[lt]=Išpakuoti archyvą į... +Name[lv]=Atspiest arhīvu uz... +Name[mr]=यावर संग्रह पुर्ववत करा... +Name[nb]=Pakk ut arkivet til … +Name[nds]=Archiev utpacken as... +Name[nl]=Archief uitpakken naar... +Name[nn]=Pakk ut arkivet til … +Name[pa]=...ਵਜੋਂ ਅਕਾਇਵ ਐਕਸਟਰੈਕਟ +Name[pl]=Rozpakuj archiwum do... +Name[pt]=Extrair o Pacote Para... +Name[pt_BR]=Extrair arquivo para... +Name[ro]=Extrage arhiva în... +Name[ru]=Указать путь... +Name[sk]=Rozbaliť archív do... +Name[sl]=Razširi arhiv v ... +Name[sq]=Ekstrakto Arkivin Tek... +Name[sr]=Распакуј архиву у... +Name[sr@ijekavian]=Распакуј архиву у... +Name[sr@ijekavianlatin]=Raspakuj arhivu u... +Name[sr@latin]=Raspakuj arhivu u... +Name[sv]=Packa upp arkiv i... +Name[th]=คลายแฟ้มจัดเก็บไปยัง... +Name[tr]=Arşivi Şuraya Çıkart... +Name[uk]=Видобути архів до... +Name[wa]=Saetchî l' årtchive foû eyet l' rilomer... +Name[x-test]=xxExtract Archive To...xx +Name[zh_CN]=解压缩归档到... +Name[zh_TW]=解壓縮到... +Icon=ark +Exec=ark --batch --autodestination --dialog %F + +[Desktop Action arkAutoExtractHere] +Name=Extract Archive Here, Autodetect Subfolder +Name[ar]=فك الأرشيف هنا ، تعرف تلقائي للمجلدات الفرعية +Name[ast]=Estrayer archivu comprimíu equí, autodeteutar subcarpeta +Name[bg]=Извличане тук, автоматична подпапка +Name[bs]=Raspakuj arhivu ovdje, pogodi poddirektorij +Name[ca]=Extreu l'arxiu aquí, detecta automàticament la subcarpeta +Name[ca@valencia]=Extrau l'arxiu ací, detecta automàticament la subcarpeta +Name[cs]=Rozbalit archiv sem, automaticky detekovat podsložku +Name[da]=Udpak arkiv her og find automatisk undermappe +Name[de]=Archiv hierher auspacken, Unterordner selbständig ermitteln +Name[el]=Εξαγωγή αρχειοθήκης εδώ, αυτόματη επιλογή υποφακέλου +Name[en_GB]=Extract Archive Here, Autodetect Subfolder +Name[es]=Extraer archivo comprimido aquí, autodetectar subcarpeta +Name[et]=Paki arhiiv siia lahti, tuvasta automaatselt alamkataloog +Name[eu]=Atera artxiboa hona, automatikoki detektatu azpikarpeta +Name[fi]=Pura paketti tähän, tunnista alikansio automaattisesti +Name[fr]=Extraire l'archive ici, auto-détecter les sous-dossiers +Name[ga]=Bain an chartlann amach agus cuir é anseo, braith fofhillteán go huathoibríoch +Name[gl]=Extraer o arquivo aquí, detectar o subcartafol +Name[hr]=Ovdje otpakiraj arhivu i automatski prepoznaj podmapu +Name[hu]=Kibontás ide (automatikus almappalétrehozás) +Name[ia]=Extrahe archivo hic, auto-releva subdossier +Name[id]=Ekstrak Arsip di Sini, Deteksi Otomatis Subfolder +Name[it]=Estrai l'archivio qui, autorileva la sottocartella +Name[ja]=アーカイブをここに展開、サブフォルダを自動検出 +Name[kk]=Мынаған, ішкі қапшығына тарқату +Name[km]=ស្រង់ចេញ​ប័ណ្ណសារ​នៅ​ទីនេះ រកថត​រង​ឃើញ​ដោយ​ស្វ័យ​ប្រវត្តិ +Name[ko]=하위 폴더를 감지해서 여기에 압축 풀기 +Name[lt]=Išpakuoti archyvą čia, automatiškai rasti poaplankius +Name[lv]=Atspiest arhīvu šeit, automātiska apakšmape +Name[mr]=येथे संग्रह पुर्ववत करा, उपसंचयीका स्वंयंशोध +Name[nb]=Pakk ut arkivet her, finn undermapper automatisk +Name[nds]=Archiev hier utpacken, Ünnerorner autom. opdecken +Name[nl]=Archief hier uitpakken, submap autodetecteren +Name[nn]=Pakk ut arkivet her / til undermappe +Name[pa]=ਅਕਾਇਵ ਇੱਥੇ ਖੋਲ੍ਹੋ, ਸਬ-ਫੋਲਡਰ ਆਪੇ ਖੋਜੋ +Name[pl]=Rozpakuj archiwum tutaj, wykryj podkatalogi +Name[pt]=Extrair o Pacote Aqui com Detecção da Sub-Pasta +Name[pt_BR]=Extrair aqui detectando subpasta +Name[ro]=Extrage arhiva aici, autodetectează subdosarul +Name[ru]=Во вложенную папку +Name[sk]=Rozbaliť archív sem, automaticky určiť podpriečinok +Name[sl]=Razširi arhiv sem, samodejno zaznaj podmapo +Name[sq]=Ekstrakto Arkivin Këtu, Vetëdallo Nëndosjen +Name[sr]=Распакуј архиву овде, погоди потфасциклу +Name[sr@ijekavian]=Распакуј архиву овдје, погоди потфасциклу +Name[sr@ijekavianlatin]=Raspakuj arhivu ovdje, pogodi potfasciklu +Name[sr@latin]=Raspakuj arhivu ovde, pogodi potfasciklu +Name[sv]=Packa upp arkiv här, detektera underkatalog automatiskt +Name[th]=คลายแฟ้มจัดเก็บไว้ที่นี่, และตรวจสอบโฟลเดอร์ย่อยอัตโนมัติ +Name[tr]=Arşivi Buraya Çıkart, Alt Dizinleri Otomatik Belirle +Name[uk]=Видобути архів сюди, автоматично визначити підтеку +Name[wa]=Saetchî l' årtchive foû cial, deteccion otomatike do ridant efant +Name[x-test]=xxExtract Archive Here, Autodetect Subfolderxx +Name[zh_CN]=在此解压缩归档,自动探测子文件夹 +Name[zh_TW]=在此解壓縮,自動偵測子資料夾 +Icon=ark +Exec=ark --batch --autodestination --autosubfolder %F diff --git a/ark/app/arkui.rc b/ark/app/arkui.rc new file mode 100644 index 00000000..6775c331 --- /dev/null +++ b/ark/app/arkui.rc @@ -0,0 +1,17 @@ + + + + + + + + + + + + + Main Toolbar + + + + diff --git a/ark/app/batchextract.cpp b/ark/app/batchextract.cpp new file mode 100644 index 00000000..9e466350 --- /dev/null +++ b/ark/app/batchextract.cpp @@ -0,0 +1,292 @@ +/* + * ark -- archiver for the KDE project + * + * Copyright (C) 2008 Harald Hvaal + * Copyright (C) 2009-2010 Raphael Kubo da Costa + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ( INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION ) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "batchextract.h" + +#include "kerfuffle/archive.h" +#include "kerfuffle/extractiondialog.h" +#include "kerfuffle/jobs.h" +#include "kerfuffle/queries.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +BatchExtract::BatchExtract() + : KCompositeJob(0), + m_autoSubfolder(false), + m_preservePaths(true), + m_openDestinationAfterExtraction(false) +{ + setCapabilities(KJob::Killable); + + connect(this, SIGNAL(result(KJob*)), SLOT(showFailedFiles())); +} + +BatchExtract::~BatchExtract() +{ + if (!m_inputs.isEmpty()) { + KIO::getJobTracker()->unregisterJob(this); + } +} + +void BatchExtract::addExtraction(Kerfuffle::Archive* archive) +{ + QString destination = destinationFolder(); + + if ((autoSubfolder()) && (!archive->isSingleFolderArchive())) { + const QDir d(destination); + QString subfolderName = archive->subfolderName(); + + if (d.exists(subfolderName)) { + subfolderName = KIO::RenameDialog::suggestName(destination, subfolderName); + } + + d.mkdir(subfolderName); + + destination += QLatin1Char( '/' ) + subfolderName; + } + + Kerfuffle::ExtractionOptions options; + options[QLatin1String( "PreservePaths" )] = preservePaths(); + + Kerfuffle::ExtractJob *job = archive->copyFiles(QVariantList(), destination, options); + + kDebug() << QString(QLatin1String( "Registering job from archive %1, to %2, preservePaths %3" )).arg(archive->fileName()).arg(destination).arg(preservePaths()); + + addSubjob(job); + + m_fileNames[job] = qMakePair(archive->fileName(), destination); + + connect(job, SIGNAL(percent(KJob*,ulong)), + this, SLOT(forwardProgress(KJob*,ulong))); + connect(job, SIGNAL(userQuery(Kerfuffle::Query*)), + this, SLOT(slotUserQuery(Kerfuffle::Query*))); +} + +void BatchExtract::slotUserQuery(Kerfuffle::Query *query) +{ + query->execute(); +} + +bool BatchExtract::autoSubfolder() const +{ + return m_autoSubfolder; +} + +void BatchExtract::setAutoSubfolder(bool value) +{ + m_autoSubfolder = value; +} + +void BatchExtract::start() +{ + QTimer::singleShot(0, this, SLOT(slotStartJob())); +} + +void BatchExtract::slotStartJob() +{ + // If none of the archives could be loaded, there is no subjob to run + if (m_inputs.isEmpty()) { + emitResult(); + return; + } + + foreach(Kerfuffle::Archive *archive, m_inputs) { + addExtraction(archive); + } + + KIO::getJobTracker()->registerJob(this); + + emit description(this, + i18n("Extracting file..."), + qMakePair(i18n("Source archive"), m_fileNames.value(subjobs().at(0)).first), + qMakePair(i18n("Destination"), m_fileNames.value(subjobs().at(0)).second) + ); + + m_initialJobCount = subjobs().size(); + + kDebug() << "Starting first job"; + + subjobs().at(0)->start(); +} + +void BatchExtract::showFailedFiles() +{ + if (!m_failedFiles.isEmpty()) { + KMessageBox::informationList(0, i18n("The following files could not be extracted:"), m_failedFiles); + } +} + +void BatchExtract::slotResult(KJob *job) +{ + kDebug(); + + // TODO: The user must be informed about which file caused the error, and that the other files + // in the queue will not be extracted. + if (job->error()) { + kDebug() << "There was en error, " << job->errorText(); + + setErrorText(job->errorText()); + setError(job->error()); + + removeSubjob(job); + + KMessageBox::error(NULL, job->errorText().isEmpty() ? + i18n("There was an error during extraction.") : job->errorText() + ); + + emitResult(); + + return; + } else { + removeSubjob(job); + } + + if (!hasSubjobs()) { + if (openDestinationAfterExtraction()) { + KUrl destination(destinationFolder()); + destination.cleanPath(); + KRun::runUrl(destination, QLatin1String( "inode/directory" ), 0); + } + + kDebug() << "Finished, emitting the result"; + emitResult(); + } else { + kDebug() << "Starting the next job"; + emit description(this, + i18n("Extracting file..."), + qMakePair(i18n("Source archive"), m_fileNames.value(subjobs().at(0)).first), + qMakePair(i18n("Destination"), m_fileNames.value(subjobs().at(0)).second) + ); + subjobs().at(0)->start(); + } +} + +void BatchExtract::forwardProgress(KJob *job, unsigned long percent) +{ + Q_UNUSED(job) + int jobPart = 100 / m_initialJobCount; + setPercent(jobPart *(m_initialJobCount - subjobs().size()) + percent / m_initialJobCount); +} + +bool BatchExtract::addInput(const KUrl& url) +{ + Kerfuffle::Archive *archive = Kerfuffle::Archive::create(url.pathOrUrl(), this); + + if ((archive == NULL) || (!QFileInfo(url.pathOrUrl()).exists())) { + m_failedFiles.append(url.fileName()); + return false; + } + + m_inputs.append(archive); + + return true; +} + +bool BatchExtract::openDestinationAfterExtraction() const +{ + return m_openDestinationAfterExtraction; +} + +bool BatchExtract::preservePaths() const +{ + return m_preservePaths; +} + +QString BatchExtract::destinationFolder() const +{ + if (m_destinationFolder.isEmpty()) { + return QDir::currentPath(); + } else { + return m_destinationFolder; + } +} + +void BatchExtract::setDestinationFolder(const QString& folder) +{ + if (QFileInfo(folder).isDir()) { + m_destinationFolder = folder; + } +} + +void BatchExtract::setOpenDestinationAfterExtraction(bool value) +{ + m_openDestinationAfterExtraction = value; +} + +void BatchExtract::setPreservePaths(bool value) +{ + m_preservePaths = value; +} + +bool BatchExtract::showExtractDialog() +{ + QWeakPointer dialog = + new Kerfuffle::ExtractionDialog; + + if (m_inputs.size() > 1) { + dialog.data()->batchModeOption(); + } + + dialog.data()->setAutoSubfolder(autoSubfolder()); + dialog.data()->setCurrentUrl(destinationFolder()); + dialog.data()->setPreservePaths(preservePaths()); + + if (m_inputs.size() == 1) { + if (m_inputs.at(0)->isSingleFolderArchive()) { + dialog.data()->setSingleFolderArchive(true); + } + dialog.data()->setSubfolder(m_inputs.at(0)->subfolderName()); + } + + if (!dialog.data()->exec()) { + delete dialog.data(); + return false; + } + + setAutoSubfolder(dialog.data()->autoSubfolders()); + setDestinationFolder(dialog.data()->destinationDirectory().pathOrUrl()); + setOpenDestinationAfterExtraction(dialog.data()->openDestinationAfterExtraction()); + setPreservePaths(dialog.data()->preservePaths()); + + delete dialog.data(); + + return true; +} + +#include diff --git a/ark/app/batchextract.h b/ark/app/batchextract.h new file mode 100644 index 00000000..12e35254 --- /dev/null +++ b/ark/app/batchextract.h @@ -0,0 +1,234 @@ +/* + * ark -- archiver for the KDE project + * + * Copyright (C) 2008 Harald Hvaal + * Copyright (C) 2009-2010 Raphael Kubo da Costa + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ( INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION ) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef BATCHEXTRACT_H +#define BATCHEXTRACT_H + +#include +#include + +#include +#include +#include +#include + +namespace Kerfuffle +{ +class Archive; +class Query; +} + +/** + * This class schedules the extraction of all given compressed archives. + * + * Like AddToArchive, this class does not need the GUI to be active, and + * provides the functionality available from the --batch command-line option. + * + * @author Harald Hvaal + */ +class BatchExtract : public KCompositeJob +{ + Q_OBJECT + +public: + /** + * Creates a new BatchExtract object. + */ + BatchExtract(); + + /** + * Destroys a BatchExtract object. + */ + virtual ~BatchExtract(); + + /** + * Creates an ExtractJob for the given @p archive and puts it on the queue. + * + * If necessary, the destination directory for the archive is created. + * + * @param archive The archive that will be extracted. + * + * @see setAutoSubfolder + */ + void addExtraction(Kerfuffle::Archive* archive); + + /** + * A wrapper that calls slotStartJob() when the event loop has started. + */ + void start(); + + /** + * Whether to automatically create a folder inside the destination + * directory if the archive has more than one directory or file + * at top level. + * + * @return @c true Create the subdirectory automatically. + * @return @c false Do not create the subdirectory automatically. + */ + bool autoSubfolder() const; + + /** + * Set whether a folder should be created when necessary so + * the archive is extracted to it. + * + * If set to @c true, when the archive does not consist of a + * single folder with the other files and directories inside, + * a directory will be automatically created inside the destination + * directory and the archive will be extracted there. + * + * @param value Whether to create this directory automatically + * when needed. + */ + void setAutoSubfolder(bool value); + + /** + * Adds a file to the list of files that will be extracted. + * + * @param url The file that will be added to the list. + * + * @return @c true The file exists and a suitable plugin + * could be found for it. + * @return @c false The file does not exist or a suitable + * plugin could not be found. + */ + bool addInput(const KUrl& url); + + /** + * Shows the extract options dialog before extracting the files. + * + * @return @c true The user has set some options and clicked OK. + * @return @c false The user has canceled extraction. + */ + bool showExtractDialog(); + + /** + * Returns the destination directory where the archives + * will be extracted to. + * + * @return The destination directory. If no directory has been manually + * set with setDestinationFolder, QDir::currentPath() will be + * returned. + */ + QString destinationFolder() const; + + /** + * Sets the directory the archives will be extracted to. + * + * If @c setSubfolder has been used, the final destination + * directory will be the concatenation of both. + * + * If @p folder does not exist, the current destination + * folder will not change. + * + * @param folder The directory that will be used. + */ + void setDestinationFolder(const QString& folder); + + /** + * Returns whether the destination folder should + * be open after all archives are extracted. + * + * @return @c true Open the destination folder. + * @return @c false Do not open the destination folder. + */ + bool openDestinationAfterExtraction() const; + + /** + * Whether to open the destination folder after + * all archives are extracted. + * + * @param value Whether to open the destination. + */ + void setOpenDestinationAfterExtraction(bool value); + + /** + * Whether all files should be extracted to the same directory, + * even if they're in different directories in the archive. + * + * This is also known as "flat" extraction. + * + * @return @c true Paths should be preserved. + * @return @c false Paths should be ignored. + */ + bool preservePaths() const; + + /** + * Sets whether paths should be preserved during extraction. + * + * When it is set to false, all files are extracted to a single + * directory, regardless of their hierarchy in the archive. + * + * @param value Whether to preserve paths. + */ + void setPreservePaths(bool value); + +private slots: + /** + * Updates the percentage of the job that has been completed. + */ + void forwardProgress(KJob *job, unsigned long percent); + + /** + * Shows a dialog with a list of all the files that could not + * be successfully extracted. + */ + void showFailedFiles(); + + /** + * Shows an error message if the current job hasn't finished + * successfully, and advances to the next extraction job if + * there are more. + */ + void slotResult(KJob *job); + + /** + * Shows a query dialog, which may happen when a file already exists. + */ + void slotUserQuery(Kerfuffle::Query *query); + + /** + * Does the real work for start() and extracts all scheduled files. + * + * Each extraction job is started after the last one finishes. + * The jobs are executed in the order they were added via addInput(). + */ + void slotStartJob(); + +private: + int m_initialJobCount; + QMap > m_fileNames; + bool m_autoSubfolder; + + QList m_inputs; + QString m_destinationFolder; + QStringList m_failedFiles; + bool m_preservePaths; + bool m_openDestinationAfterExtraction; +}; + +#endif // BATCHEXTRACT_H diff --git a/ark/app/extractHereDndPlugin.cpp b/ark/app/extractHereDndPlugin.cpp new file mode 100644 index 00000000..685a6b7d --- /dev/null +++ b/ark/app/extractHereDndPlugin.cpp @@ -0,0 +1,82 @@ +/* + * ark -- archiver for the KDE project + * + * Copyright (C) 2009 Harald Hvaal + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "extractHereDndPlugin.h" +#include "batchextract.h" +#include "kerfuffle/archive.h" + +#include +#include +#include +#include +#include +#include + +K_PLUGIN_FACTORY(ExtractHerePluginFactory, + registerPlugin(); + ) +K_EXPORT_PLUGIN(ExtractHerePluginFactory("stupidname", "ark")) + +void ExtractHereDndPlugin::slotTriggered() +{ + kDebug() << "Preparing job"; + BatchExtract *batchJob = new BatchExtract(); + + batchJob->setAutoSubfolder(true); + batchJob->setDestinationFolder(m_dest.pathOrUrl()); + batchJob->setPreservePaths(true); + foreach(const KUrl& url, m_urls) { + batchJob->addInput(url); + } + + batchJob->start(); + kDebug() << "Started job"; + +} + +ExtractHereDndPlugin::ExtractHereDndPlugin(QObject* parent, const QVariantList&) + : KonqDndPopupMenuPlugin(parent) +{ +} + +void ExtractHereDndPlugin::setup(const KFileItemListProperties& popupMenuInfo, + KUrl destination, + QList& userActions) +{ + const QString extractHereMessage = i18nc("@action:inmenu Context menu shown when an archive is being drag'n'dropped", "Extract here"); + + if (!Kerfuffle::supportedMimeTypes().contains(popupMenuInfo.mimeType())) { + kDebug() << popupMenuInfo.mimeType() << "is not a supported mimetype"; + return; + } + + kDebug() << "Plugin executed"; + + KAction *action = new KAction(KIcon(QLatin1String("archive-extract")), + extractHereMessage, NULL); + connect(action, SIGNAL(triggered()), this, SLOT(slotTriggered())); + + userActions.append(action); + m_dest = destination; + m_urls = popupMenuInfo.urlList(); +} + +#include "extractHereDndPlugin.moc" diff --git a/ark/app/extractHereDndPlugin.h b/ark/app/extractHereDndPlugin.h new file mode 100644 index 00000000..0df2bce0 --- /dev/null +++ b/ark/app/extractHereDndPlugin.h @@ -0,0 +1,46 @@ +/* + * ark -- archiver for the KDE project + * + * Copyright (C) 2009 Harald Hvaal + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef EXTRACTHEREDNDPLUGIN_H +#define EXTRACTHEREDNDPLUGIN_H + +#include +#include + +class ExtractHereDndPlugin : public KonqDndPopupMenuPlugin +{ + Q_OBJECT + +private slots: + void slotTriggered(); + +public: + ExtractHereDndPlugin(QObject* parent, const QVariantList&); + + virtual void setup(const KFileItemListProperties& popupMenuInfo, + KUrl destination, + QList& userActions); +private: + KUrl m_dest; + QList m_urls; +}; + +#endif /* EXTRACTHEREDNDPLUGIN_H */ diff --git a/ark/app/icons/CMakeLists.txt b/ark/app/icons/CMakeLists.txt new file mode 100644 index 00000000..02e3c9ec --- /dev/null +++ b/ark/app/icons/CMakeLists.txt @@ -0,0 +1 @@ +kde4_install_icons(${ICON_INSTALL_DIR}) diff --git a/ark/app/icons/hi128-apps-ark.png b/ark/app/icons/hi128-apps-ark.png new file mode 100644 index 0000000000000000000000000000000000000000..f74d0cfd65bb53e18c9694609021c18123e18af9 GIT binary patch literal 13215 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%0Ac~jT=@OOeguFY zOZU0JbDk(k{kCU%!NhmKxb9(Z=_FlpCM8!9fj%O2a6&+y8+Hu>Vm1uTnR>N6Wwen}iChl!9QW2Dp)U;#M{e$EIIqq~$a z+Va&%{Et@l!+mOl%7n0pu=?!o_tHIMbnKj` z*!nIJy(H#)0(_l4ZhB6iB*Hg+H|g1sZ4*t8J;wBh4De01ivGX(( zd5;8R0n9h-(83+amsCx+O*}|iTMf5_r9Ca&Lk4)ujIf4D)rcl^*rSk2Ni__^qp8#H zYbh@f__CDpVi=fHm2xy*Dg{C*1tD^sAahqxDp;?u)m{O44`TX%kE}_px2dNNh@X4r zTY$gL%y-ziX94-v%h>N)xXa7*-7)S{qHi}A=idv_}oD||*5a^Q`BUGbuS$G(9T5B9uoFdwr~#t@Cyb4Fe5i2t#o8f zU{ES6)9^+f1MAZQbe@I$fWZ??9B1W;0NwV2?hEqEgLu#pJri>suF-t%aSi-Xe7}Wz2!LNH5Qdq@ z>A77XG&h|oSG}qEQrI(zk)*wklZ0KG2*rD<`$GWyMi^luIwM2BEfZyhlT}eueJ*oA z8*Z5=2G~*ez^K(90^m0YggaLtnoWpL@@FPaP6AnrF9iM&0KWr3{GLC9{{vE|YUety Rb*umY002ovPDHLkV1n5&>m&dG literal 0 HcmV?d00001 diff --git a/ark/app/icons/hi16-apps-ark.png b/ark/app/icons/hi16-apps-ark.png new file mode 100644 index 0000000000000000000000000000000000000000..7e5d49a6a423799ef3c0ef4b6f77605b6a4d893c GIT binary patch literal 528 zcmV+r0`L8aP)O^bC=jBrDYc2bRvjgWIjkatd!dQ+5l zN|$;~mwQl~eo&l$QJjcnoq$rFf>WM|WS@gppNnatg;%19UZaU$qmXc?iCL$LT&av* zs*PT%lyIw;b*-6qt*D)@sh+W^nX{5zvz~sqmtwoJqQ0DGzn^QrpKQOOZ^NN%!=!P< zseQ$!a>cHQ#<-!#r*X}!bT)3oN-zUR)S=hwaG+s5hP((C5i|Nj2}|Nj?@=w|=`00DGTPE!Ct=GbNc z005LpL_t&-(~Zc*8iHUHgyAtzN>oHt!otQ5>~8M=f;>NN)RpgMzGr}6!*6t40>$_I zb=ZkdHeekd=hKR;ulw850)$AmP~UA9+xsY$2@8Pk4sx;bblxvT^F3Dw)Cm!1$EL_a z1(Y%ARG*bL3k8t7#Pod6WFZ4mi)dcl(^yCV-ynwLI|d6L;A%`dlnj=m;~QRX*dsew Sy_~ZE0000P^+ literal 0 HcmV?d00001 diff --git a/ark/app/icons/hi22-apps-ark.png b/ark/app/icons/hi22-apps-ark.png new file mode 100644 index 0000000000000000000000000000000000000000..df43245b533227999a32f6c3a88c9304c6d43a4a GIT binary patch literal 1069 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 Q^Zg(H3uCG_o;ooE0BM~}j{pDw literal 0 HcmV?d00001 diff --git a/ark/app/main.cpp b/ark/app/main.cpp new file mode 100644 index 00000000..bcd187d2 --- /dev/null +++ b/ark/app/main.cpp @@ -0,0 +1,210 @@ +/* + * ark -- archiver for the KDE project + * + * Copyright (C) 2007 Henrique Pinto + * Copyright (C) 2008-2009 Harald Hvaal + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "mainwindow.h" +#include "batchextract.h" +#include "kerfuffle/addtoarchive.h" + +#include +#include +#include +#include +#include + +#include +#include + +using Kerfuffle::AddToArchive; + +int main(int argc, char **argv) +{ + KAboutData aboutData("ark", 0, ki18n("Ark"), + "2.19", ki18n("KDE Archiving tool"), + KAboutData::License_GPL, + ki18n("(c) 1997-2011, The Various Ark Developers"), + KLocalizedString(), + "http://utils.kde.org/projects/ark" + ); + + aboutData.addAuthor(ki18n("Raphael Kubo da Costa"), + ki18n("Maintainer"), + "rakuco@FreeBSD.org"); + aboutData.addAuthor(ki18n("Harald Hvaal"), + ki18n("Former Maintainer"), + "haraldhv@stud.ntnu.no"); + aboutData.addAuthor(ki18n("Henrique Pinto"), + ki18n("Former Maintainer"), + "henrique.pinto@kdemail.net"); + aboutData.addAuthor(ki18n("Helio Chissini de Castro"), + ki18n("Former maintainer"), + "helio@kde.org"); + aboutData.addAuthor(ki18n("Georg Robbers"), + KLocalizedString(), + "Georg.Robbers@urz.uni-hd.de"); + aboutData.addAuthor(ki18n("Roberto Selbach Teixeira"), + KLocalizedString(), + "maragato@kde.org"); + aboutData.addAuthor(ki18n("Francois-Xavier Duranceau"), + KLocalizedString(), + "duranceau@kde.org"); + aboutData.addAuthor(ki18n("Emily Ezust (Corel Corporation)"), + KLocalizedString(), + "emilye@corel.com"); + aboutData.addAuthor(ki18n("Michael Jarrett (Corel Corporation)"), + KLocalizedString(), + "michaelj@corel.com"); + aboutData.addAuthor(ki18n("Robert Palmbos"), + KLocalizedString(), + "palm9744@kettering.edu"); + + aboutData.addCredit(ki18n("Bryce Corkins"), + ki18n("Icons"), + "dbryce@attglobal.net"); + aboutData.addCredit(ki18n("Liam Smit"), + ki18n("Ideas, help with the icons"), + "smitty@absamail.co.za"); + aboutData.addCredit(ki18n("Andrew Smith"), + ki18n("bkisofs code"), + QByteArray(), + "http://littlesvr.ca/misc/contactandrew.php"); + aboutData.setProgramIconName(QLatin1String("ark")); + + KCmdLineArgs::init(argc, argv, &aboutData); + KCmdLineOptions option; + option.add("+[url]", ki18n("URL of an archive to be opened")); + option.add("d").add("dialog", ki18n("Show a dialog for specifying the options for the operation (extract/add)")); + option.add("o").add("destination ", ki18n("Destination folder to extract to. Defaults to current path if not specified.")); + option.add(":", ki18n("Options for adding files")); + option.add("c").add("add", ki18n("Query the user for an archive filename and add specified files to it. Quit when finished.")); + option.add("t").add("add-to ", ki18n("Add the specified files to 'filename'. Create archive if it does not exist. Quit when finished.")); + option.add("p").add("changetofirstpath", ki18n("Change the current dir to the first entry and add all other entries relative to this one.")); + option.add("f").add("autofilename ", ki18n("Automatically choose a filename, with the selected suffix (for example rar, tar.gz, zip or any other supported types)")); + option.add(":", ki18n("Options for batch extraction:")); + option.add("b").add("batch", ki18n("Use the batch interface instead of the usual dialog. This option is implied if more than one url is specified.")); + option.add("e").add("autodestination", ki18n("The destination argument will be set to the path of the first file supplied.")); + option.add("a").add("autosubfolder", ki18n("Archive contents will be read, and if detected to not be a single folder archive, a subfolder with the name of the archive will be created.")); + KCmdLineArgs::addCmdLineOptions(option); + KCmdLineArgs::addTempFileOption(); + + KApplication application; + application.setQuitOnLastWindowClosed(false); + + //session restoring + if (application.isSessionRestored()) { + MainWindow* window = NULL; + + if (KMainWindow::canBeRestored(1)) { + window = new MainWindow; + window->restore(1); + if (!window->loadPart()) { + delete window; + window = NULL; + } + } + + if (window == NULL) { + return -1; + } + } else { //new ark window (no restored session) + // open any given URLs + KCmdLineArgs *args = KCmdLineArgs::parsedArgs(); + + if (args->isSet("add") || args->isSet("add-to")) { + AddToArchive *addToArchiveJob = new AddToArchive; + application.connect(addToArchiveJob, SIGNAL(result(KJob*)), SLOT(quit()), Qt::QueuedConnection); + + if (args->isSet("changetofirstpath")) { + addToArchiveJob->setChangeToFirstPath(true); + } + + if (args->isSet("add-to")) { + addToArchiveJob->setFilename(args->getOption("add-to")); + } + + if (args->isSet("autofilename")) { + addToArchiveJob->setAutoFilenameSuffix(args->getOption("autofilename")); + } + + for (int i = 0; i < args->count(); ++i) { + //TODO: use the returned value here? + addToArchiveJob->addInput(args->url(i)); + } + + if (args->isSet("dialog")) { + if (!addToArchiveJob->showAddDialog()) { + return 0; + } + } + + addToArchiveJob->start(); + } else if (args->isSet("batch")) { + BatchExtract *batchJob = new BatchExtract; + application.connect(batchJob, SIGNAL(result(KJob*)), SLOT(quit()), Qt::QueuedConnection); + + for (int i = 0; i < args->count(); ++i) { + batchJob->addInput(args->url(i)); + } + + if (args->isSet("autosubfolder")) { + kDebug() << "Setting autosubfolder"; + batchJob->setAutoSubfolder(true); + } + + if (args->isSet("autodestination")) { + QString autopath = QFileInfo(args->url(0).path()).path(); + kDebug() << "By autodestination, setting path to " << autopath; + batchJob->setDestinationFolder(autopath); + } + + if (args->isSet("destination")) { + kDebug() << "Setting destination to " << args->getOption("destination"); + batchJob->setDestinationFolder(args->getOption("destination")); + } + + if (args->isSet("dialog")) { + if (!batchJob->showExtractDialog()) { + return 0; + } + } + + batchJob->start(); + } else { + MainWindow *window = new MainWindow; + if (!window->loadPart()) { // if loading the part fails + return -1; + } + + if (args->count()) { + kDebug() << "trying to open" << args->url(0); + + if (args->isSet("dialog")) { + window->setShowExtractDialog(true); + } + window->openUrl(args->url(0)); + } + window->show(); + } + } + + kDebug() << "Entering application loop"; + return application.exec(); +} diff --git a/ark/app/mainwindow.cpp b/ark/app/mainwindow.cpp new file mode 100644 index 00000000..aee82317 --- /dev/null +++ b/ark/app/mainwindow.cpp @@ -0,0 +1,259 @@ +/* + * ark -- archiver for the KDE project + * + * Copyright (C) 2002-2003: Georg Robbers + * Copyright (C) 2003: Helio Chissini de Castro + * Copyright (C) 2007 Henrique Pinto + * Copyright (C) 2008 Harald Hvaal + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ +#include "mainwindow.h" +#include "kerfuffle/archive.h" +#include "part/interface.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +static bool isValidArchiveDrag(const QMimeData *data) +{ + return ((data->hasUrls()) && (data->urls().count() == 1)); +} + +MainWindow::MainWindow(QWidget *) + : KParts::MainWindow() +{ + setXMLFile(QLatin1String( "arkui.rc" )); + + setupActions(); + statusBar(); + + if (!initialGeometrySet()) { + resize(640, 480); + } + setAutoSaveSettings(QLatin1String( "MainWindow" )); + + setAcceptDrops(true); +} + +MainWindow::~MainWindow() +{ + if (m_recentFilesAction) { + m_recentFilesAction->saveEntries(KGlobal::config()->group("Recent Files")); + } + delete m_part; + m_part = 0; +} + +void MainWindow::dragEnterEvent(QDragEnterEvent * event) +{ + kDebug() << event; + + Interface *iface = qobject_cast(m_part); + if (iface->isBusy()) { + return; + } + + if ((event->source() == NULL) && + (isValidArchiveDrag(event->mimeData()))) { + event->acceptProposedAction(); + } + return; +} + +void MainWindow::dropEvent(QDropEvent * event) +{ + kDebug() << event; + + Interface *iface = qobject_cast(m_part); + if (iface->isBusy()) { + return; + } + + if ((event->source() == NULL) && + (isValidArchiveDrag(event->mimeData()))) { + event->acceptProposedAction(); + } + + //TODO: if this call provokes a message box the drag will still be going + //while the box is onscreen. looks buggy, do something about it + openUrl(event->mimeData()->urls().at(0)); +} + +void MainWindow::dragMoveEvent(QDragMoveEvent * event) +{ + kDebug() << event; + + Interface *iface = qobject_cast(m_part); + if (iface->isBusy()) { + return; + } + + if ((event->source() == NULL) && + (isValidArchiveDrag(event->mimeData()))) { + event->acceptProposedAction(); + } +} + +bool MainWindow::loadPart() +{ + KPluginLoader loader(QLatin1String( "arkpart" )); + KPluginFactory *factory = loader.factory(); + if (factory) { + m_part = static_cast(factory->create(this)); + } + if (!factory || !m_part) { + KMessageBox::error(this, i18n("Unable to find Ark's KPart component, please check your installation.")); + kWarning() << "Error loading Ark KPart: " << loader.errorString(); + return false; + } + + m_part->setObjectName( QLatin1String("ArkPart" )); + setCentralWidget(m_part->widget()); + createGUI(m_part); + + connect(m_part, SIGNAL(busy()), this, SLOT(updateActions())); + connect(m_part, SIGNAL(ready()), this, SLOT(updateActions())); + connect(m_part, SIGNAL(quit()), this, SLOT(quit())); + + return true; +} + +void MainWindow::setupActions() +{ + m_newAction = KStandardAction::openNew(this, SLOT(newArchive()), actionCollection()); + m_openAction = KStandardAction::open(this, SLOT(openArchive()), actionCollection()); + KStandardAction::quit(this, SLOT(quit()), actionCollection()); + + m_recentFilesAction = KStandardAction::openRecent(this, SLOT(openUrl(KUrl)), actionCollection()); + m_recentFilesAction->setToolBarMode(KRecentFilesAction::MenuMode); + m_recentFilesAction->setToolButtonPopupMode(QToolButton::DelayedPopup); + m_recentFilesAction->setIconText(i18nc("action, to open an archive", "Open")); + m_recentFilesAction->setStatusTip(i18n("Click to open an archive, click and hold to open a recently-opened archive")); + m_recentFilesAction->setToolTip(i18n("Open an archive")); + m_recentFilesAction->loadEntries(KGlobal::config()->group("Recent Files")); + connect(m_recentFilesAction, SIGNAL(triggered()), + this, SLOT(openArchive())); + + createStandardStatusBarAction(); + + KStandardAction::configureToolbars(this, SLOT(editToolbars()), actionCollection()); + KStandardAction::keyBindings(this, SLOT(editKeyBindings()), actionCollection()); +} + +void MainWindow::updateActions() +{ + Interface *iface = qobject_cast(m_part); + m_newAction->setEnabled(!iface->isBusy()); + m_openAction->setEnabled(!iface->isBusy()); + m_recentFilesAction->setEnabled(!iface->isBusy()); +} + +void MainWindow::editKeyBindings() +{ + KShortcutsDialog dlg(KShortcutsEditor::AllActions, KShortcutsEditor::LetterShortcutsAllowed, this); + dlg.addCollection(actionCollection()); + dlg.addCollection(m_part->actionCollection()); + + dlg.configure(); +} + +void MainWindow::editToolbars() +{ + saveMainWindowSettings(KGlobal::config()->group(QLatin1String("MainWindow"))); + + QWeakPointer dlg = new KEditToolBar(factory(), this); + dlg.data()->exec(); + + createGUI(m_part); + + applyMainWindowSettings(KGlobal::config()->group(QLatin1String("MainWindow"))); + + delete dlg.data(); +} + +void MainWindow::openArchive() +{ + Interface *iface = qobject_cast(m_part); + Q_ASSERT(iface); + const KUrl url = KFileDialog::getOpenUrl(KUrl("kfiledialog:///ArkOpenDir"), + Kerfuffle::supportedMimeTypes().join( QLatin1String( " " )), + this); + openUrl(url); +} + +void MainWindow::openUrl(const KUrl& url) +{ + if (!url.isEmpty()) { + m_part->setArguments(m_openArgs); + + if (m_part->openUrl(url)) { + m_recentFilesAction->addUrl(url); + } else { + m_recentFilesAction->removeUrl(url); + } + } +} + +void MainWindow::setShowExtractDialog(bool option) +{ + if (option) { + m_openArgs.metaData()[QLatin1String( "showExtractDialog" )] = QLatin1String( "true" ); + } else { + m_openArgs.metaData().remove(QLatin1String( "showExtractDialog" )); + } +} + +void MainWindow::quit() +{ + close(); +} + +void MainWindow::newArchive() +{ + Interface *iface = qobject_cast(m_part); + Q_ASSERT(iface); + + const QStringList mimeTypes = Kerfuffle::supportedWriteMimeTypes(); + + kDebug() << "Supported mimetypes are" << mimeTypes.join( QLatin1String( " " )); + + const KUrl saveFileUrl = + KFileDialog::getSaveUrl(KUrl("kfiledialog:///ArkNewDir"), + mimeTypes.join(QLatin1String(" "))); + + m_openArgs.metaData()[QLatin1String( "createNewArchive" )] = QLatin1String( "true" ); + + openUrl(saveFileUrl); + + m_openArgs.metaData().remove(QLatin1String( "showExtractDialog" )); + m_openArgs.metaData().remove(QLatin1String( "createNewArchive" )); +} diff --git a/ark/app/mainwindow.h b/ark/app/mainwindow.h new file mode 100644 index 00000000..5dd7fb82 --- /dev/null +++ b/ark/app/mainwindow.h @@ -0,0 +1,66 @@ +/* + * ark -- archiver for the KDE project + * + * Copyright (C) 2007 Henrique Pinto + * Copyright (C) 2008 Harald Hvaal + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include +#include +#include + +class KRecentFilesAction; + +class MainWindow: public KParts::MainWindow +{ + Q_OBJECT +public: + MainWindow(QWidget *parent = 0); + ~MainWindow(); + bool loadPart(); + + void dragEnterEvent(class QDragEnterEvent * event); + void dropEvent(class QDropEvent * event); + void dragMoveEvent(class QDragMoveEvent * event); + +public slots: + void openUrl(const KUrl& url); + void setShowExtractDialog(bool); + +private slots: + void updateActions(); + void newArchive(); + void openArchive(); + void quit(); + + void editKeyBindings(); + void editToolbars(); + +private: + void setupActions(); + + KParts::ReadWritePart *m_part; + KRecentFilesAction *m_recentFilesAction; + QAction *m_openAction; + QAction *m_newAction; + KParts::OpenUrlArguments m_openArgs; +}; + +#endif // MAINWINDOW_H diff --git a/ark/cmake/modules/COPYING-CMAKE-SCRIPTS b/ark/cmake/modules/COPYING-CMAKE-SCRIPTS new file mode 100644 index 00000000..4b417765 --- /dev/null +++ b/ark/cmake/modules/COPYING-CMAKE-SCRIPTS @@ -0,0 +1,22 @@ +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/ark/cmake/modules/FindLibArchive.cmake b/ark/cmake/modules/FindLibArchive.cmake new file mode 100644 index 00000000..db14d7b2 --- /dev/null +++ b/ark/cmake/modules/FindLibArchive.cmake @@ -0,0 +1,46 @@ +# - Try to find libarchive +# Once done this will define +# +# LIBARCHIVE_FOUND - system has libarchive +# LIBARCHIVE_INCLUDE_DIR - the libarchive include directory +# LIBARCHIVE_LIBRARY - Link this to use libarchive +# HAVE_LIBARCHIVE_GZIP_SUPPORT - whether libarchive has been compiled with gzip support +# HAVE_LIBARCHIVE_LZMA_SUPPORT - whether libarchive has been compiled with lzma support +# HAVE_LIBARCHIVE_XZ_SUPPORT - whether libarchive has been compiled with xz support +# HAVE_LIBARCHIVE_RPM_SUPPORT - whether libarchive has been compiled with rpm support +# HAVE_LIBARCHIVE_CAB_SUPPORT - whether libarchive has been compiled with cab support +# +# Copyright (c) 2006, Pino Toscano, +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + +include(CheckLibraryExists) + +if (LIBARCHIVE_LIBRARY AND LIBARCHIVE_INCLUDE_DIR) + # in cache already + set(LIBARCHIVE_FOUND TRUE) +else (LIBARCHIVE_LIBRARY AND LIBARCHIVE_INCLUDE_DIR) + find_path(LIBARCHIVE_INCLUDE_DIR archive.h + ${GNUWIN32_DIR}/include + ) + + find_library(LIBARCHIVE_LIBRARY NAMES archive libarchive archive2 libarchive2 + PATHS + ${GNUWIN32_DIR}/lib + ) + + if (LIBARCHIVE_LIBRARY) + check_library_exists(${LIBARCHIVE_LIBRARY} archive_write_set_compression_gzip "" HAVE_LIBARCHIVE_GZIP_SUPPORT) + check_library_exists(${LIBARCHIVE_LIBRARY} archive_write_set_compression_lzma "" HAVE_LIBARCHIVE_LZMA_SUPPORT) + check_library_exists(${LIBARCHIVE_LIBRARY} archive_write_set_compression_xz "" HAVE_LIBARCHIVE_XZ_SUPPORT) + check_library_exists(${LIBARCHIVE_LIBRARY} archive_read_support_compression_rpm "" HAVE_LIBARCHIVE_RPM_SUPPORT) + check_library_exists(${LIBARCHIVE_LIBRARY} archive_read_disk_entry_from_file "" HAVE_LIBARCHIVE_READ_DISK_API) + check_library_exists(${LIBARCHIVE_LIBRARY} archive_read_support_format_cab "" HAVE_LIBARCHIVE_CAB_SUPPORT) + endif (LIBARCHIVE_LIBRARY) + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(LibArchive DEFAULT_MSG LIBARCHIVE_INCLUDE_DIR LIBARCHIVE_LIBRARY HAVE_LIBARCHIVE_GZIP_SUPPORT) + + mark_as_advanced(LIBARCHIVE_INCLUDE_DIR LIBARCHIVE_LIBRARY HAVE_LIBARCHIVE_GZIP_SUPPORT HAVE_LIBARCHIVE_LZMA_SUPPORT HAVE_LIBARCHIVE_RPM_SUPPORT HAVE_LIBARCHIVE_CAB_SUPPORT) +endif (LIBARCHIVE_LIBRARY AND LIBARCHIVE_INCLUDE_DIR) diff --git a/ark/config.h.cmake b/ark/config.h.cmake new file mode 100644 index 00000000..1d0b1cb4 --- /dev/null +++ b/ark/config.h.cmake @@ -0,0 +1,2 @@ +#cmakedefine HAVE_LIBARCHIVE_LZMA_SUPPORT ${HAVE_LIBARCHIVE_LZMA_SUPPORT} +#cmakedefine HAVE_LIBARCHIVE_XZ_SUPPORT ${HAVE_LIBARCHIVE_XZ_SUPPORT} diff --git a/ark/doc/CMakeLists.txt b/ark/doc/CMakeLists.txt new file mode 100644 index 00000000..7bcd32ef --- /dev/null +++ b/ark/doc/CMakeLists.txt @@ -0,0 +1,5 @@ +########### install files ############### +# +kde4_create_handbook(index.docbook INSTALL_DESTINATION ${HTML_INSTALL_DIR}/en SUBDIR ark) +kde4_create_manpage(man-ark.1.docbook 1 INSTALL_DESTINATION ${MAN_INSTALL_DIR}) + diff --git a/ark/doc/ark-mainwindow.png b/ark/doc/ark-mainwindow.png new file mode 100644 index 0000000000000000000000000000000000000000..0fe438f1ba64d0ff67976dac9fae5f88a5a44ff5 GIT binary patch literal 53839 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 literal 0 HcmV?d00001 diff --git a/ark/doc/index.docbook b/ark/doc/index.docbook new file mode 100644 index 00000000..0e52c953 --- /dev/null +++ b/ark/doc/index.docbook @@ -0,0 +1,336 @@ + + + + + +]> + + + + +The &ark; Handbook + + + +&Matt.Johnston; &Matt.Johnston.mail; + + + + + + +2000 +&Matt.Johnston; + + + +2004 +Henrique Pinto + + + +&FDLNotice; + +2013-06-21 +2.19 &kde; (4.11) + + +&ark; is an archive manager for &kde;. + + +KDE +gzip +gunzip +tar +archive +zip +compression +7z +kdeutils +ark + + + + +Introduction + +&ark; is a program for viewing, extracting, creating and modifying +archives. +&ark; can handle various archive formats such as +tar, gzip, +bzip2, zip, rar, +7zip, xz, rpm, +cab and deb (support for certain archive formats depend on +the appropriate command-line programs being installed). + + +&ark;'s main window + + + + + +&ark;'s main window + + + + + + + +Using &ark; + + +Opening Archives + +To open an archive in &ark;, choose +Open... (&Ctrl;O) from the File +menu. You can also open archive files by dragging and dropping from +&konqueror; or &dolphin;. Archive files should be associated with &ark;, so you can +also right click a file in &konqueror; or &dolphin; and +select Open with &ark; to open it or select an extract action for this file. + +If you have enabled the information panel in the Settings menu +additional information about the selected folders or files in the archive is displayed. + + + +Working with Files + +Once an archive has been opened, you can perform various +operations on the files inside the archive. By +selecting a file and using the Action +menu, you can choose what you want to do: + + + +Add File... will add files from your disk to the archive. + + +Add Folder... will add folders from your disk to the archive. + + +Delete (Del) will remove the currently +selected file(s) from the archive. + + +Extract... (&Ctrl;E) opens a submenu with previously accessed folders, +and you can select to quick extract into any of them. + + +Preview will open the file in the associated viewer for that file type. + + + + + + +Extracting Archives and Removing Files + +Once an archive has been opened in &ark;, it can be extracted. To +quick extract files from an archive, you can select +Extract from the +Action menu. This opens a submenu with previously accessed folders, +and you can select to quick extract into any of them. Clicking on the check mark at +the right side of the Extract button in the toolbar performes the same action. + + +To open the Extract dialog click the Extract +button in the toolbar or use the shortcut &Ctrl; +E. + +This dialog allows you to select where you will extract files to. The default +location is the folder the archive is in. +You can specify to extract the files into a subfolder. The default name of this +subfolder is the first part of the archive name, but you can edit it to your needs. +If you want to preserve paths when extracting, select this option. +You may also choose to open the destination folder +in &konqueror; or &dolphin; or close &ark; once the extraction is complete. + +If a file in the archive list is highlighted, you can +also select which files to extract: + + + +Selected files only extracts only the files +which have been selected. + + +All files extracts the entire contents of the +archive. + + +To extract a single file from an archive, click on the filename and drag it to the +target folder + + +Extracting files from an archive does not change the archive and its contents. +Use ActionDelete +(Del) for this task. + + + + + +Creating Archives and Adding Files + +To create a new archive in &ark;, choose +New from the File +menu. + +You can then type the name of the archive, with the appropriate +extension (tar.gz, zip, bz2 +&etc;) or select a supported format in the Filter combo box +and check the Automatically select filename extension option. +To add files to the archive, choose Add +File... from the Action menu. If you +want to add an entire folder to an archive, choose Add +Folder... from the Action menu. + +An alternative way to add files to the archive is to drag one or more files +from &konqueror;, &dolphin; or the desktop into the main &ark; window, and it will +be added to the current archive. + + + + + +Using &ark; in the Filemanager + +Clicking with the &RMB; on an archive in a filemanager like &dolphin; or &konqueror; +displays a context menu with an item Open with Ark. +The menu has these additional items to extract an archive using &ark;: + + + + +Extract Archive Here, Autodetect Subfolder creates a +subfolder in the folder with the archive and extracts the folders and files into it. + + +Extract Archive To... works like in &ark;. + + +Extract Archive Here extracts the content of the archive into same folder. + + + + +&dolphin;'s or &konqueror;'s context menu for a selection displays these actions in the +Compress submenu: + + + + +Here creates a Tar archive (gzip-compressed) in the current folder +with the extension .tar.gz. + + +As ZIP Archive, As RAR Archive +or As ZIP/TAR Archive creates these archive types in the current folder. + + +Compress To... opens a dialog where you can select folder, name and archive type. + + + + + + +Advanced Batch Mode +&ark; has an advanced batch mode to manage archives without launching a &GUI; interface. +This mode allows you to extract or create archives and add files to them. + +The batch mode is documented in &ark;'s man page. + + + + + +Credits and License + +&ark; is Copyright © 1997-2008, The Various &ark; Developers + + +Authors: +Raphael Kubo da Costa +rakuco@FreeBSD.org +Harald Hvaal +haraldhv@stud.ntnu.no +Helio Chissini de Castro +helio@conectiva.com.br +Georg Robbers +Georg.Robbers@urz.uni-hd.de +Henrique Pinto +henrique.pinto@kdemail.net +Roberto Selbach Teixeira +maragato@kde.org +Robert Palmbos +palm9744@kettering.edu +Francois-Xavier Duranceau +duranceau@kde.org +Corel Corporation (author: Emily Ezust) +emilye@corel.com +Corel Corporation (author: Michael Jarrett) +michaelj@corel.com + + +Documentation Copyright © 2000 &Matt.Johnston; +&Matt.Johnston.mail; + +Documentation updated for &kde; 3.3 by Henrique Pinto +henrique.pinto@kdemail.net. + + +&underFDL; +&underGPL; + + + + +Installation + + +How to obtain &ark; + +&install.intro.documentation; + + + + +Requirements + +In order to successfully use &ark;, you need &kde; +4. &GNU; tar and a recent +gzip are also needed if you want &ark; to handle tar archives. To handle other +file formats, you need the appropriate command line programs, such as bzip2, +zip, unzip, ar, rar, +7zip, xz, rpm, +cab and deb. + + + + +Compilation and Installation + +&install.compile.documentation; + + + + + +&documentation.index; + + + + diff --git a/ark/doc/man-ark.1.docbook b/ark/doc/man-ark.1.docbook new file mode 100644 index 00000000..22f8519b --- /dev/null +++ b/ark/doc/man-ark.1.docbook @@ -0,0 +1,208 @@ + + + +]> + + + +&kde; User's Manual +LauriWatts Initial version of &ark; man page. + +RaphaelKubo da Costa Update &ark; man page. + +2013-08-23 +2.19 (&kde; 4.11) +K Desktop Environment + + + +ark +1 + + + +ark +&kde; archiving tool + + + + +ark + + + + + +suffix + + +file + + +directory +&kde; Generic Options +&Qt; Generic Options + + + + +Description +&ark; is a program for managing various compressed file formats +within &kde;. Archives can be viewed, extracted, created +and modified with &ark;. The program can handle various +formats such as tar, +gzip, bzip2, +zip, rar +(when the appropriate libraries or command-line programs are +installed). + + + +Operation modes +&ark; can be used either as a stand-alone &GUI; program as well as a +command-line program in order to perform some specific tasks. +If invoked without the -b (--batch) or -c (--add) options, &ark; is started +as a normal &GUI; program. +When the -b (--batch) option is used, &ark; can be used to extract the +contents of one or more files directly from the command-line, without +launching its &GUI;. +When the -c (--add) option is used, &ark; prompts for files that should +be added to a new archive or to an existing archive. + + + + + +Options + + + + + +Show a dialog for specifying the options for a batch or add operation. + + + + +Default the extraction directory to directory. +If not passed, the current path is used. + + + + + +Options for adding files + + + + +Query the user for an archive filename and add specified files to it. +Quit when finished. + + + + + +Add the specified files to filename. Create archive +if it does not exist. Quit when finished. + + + + + +Change the current directory to the first entry and add all other entries relative +to this one. + + + + + + +Automatically choose a filename, with the selected suffix +(for example rar, tar.gz, zip or any other supported types). + + + + + + +Options for batch extraction + + + + +Use the batch interface instead of the usual dialog. This option is implied +if more than one url is specified. + + + + + + +The destination argument will be set to the path of the first file +supplied. + + + + + + +Archive contents will be read, and if detected to not be a single folder archive, +a subfolder by the name of the archive will be created. + + + + + + + + +Examples + + + +ark +archive.tar.bz2 + +Will extract archive.tar.bz2 into the current directory +without showing any &GUI;. + + + + + +ark +archive.tar.bz2 archive2.zip + +Will first show an extraction options dialog and then extract both +archive.tar.bz2 and archive2.zip +into the directory chosen in the dialog. + + + + +ark +my-archive.zip photo1.jpg +text.txt + +Will create my-archive.zip if does not exist and +then add photo1.jpg and text.txt to it. + + + + + + + + +Authors +&ark; is currently maintained by &Harald.Hvaal; &Harald.Hvaal.mail; +and &Raphael.Kubo.da.Costa; &Raphael.Kubo.da.Costa.mail;. +This man page was first written by &Lauri.Watts; +&Lauri.Watts.mail; in 2005 for &kde; 3.4, and was later updated in 2009 by +&Raphael.Kubo.da.Costa; &Raphael.Kubo.da.Costa.mail;. + + + diff --git a/ark/kerfuffle/CMakeLists.txt b/ark/kerfuffle/CMakeLists.txt new file mode 100644 index 00000000..8c56b4f4 --- /dev/null +++ b/ark/kerfuffle/CMakeLists.txt @@ -0,0 +1,50 @@ +macro_optional_find_package(QJSON) +macro_log_feature(QJSON_FOUND "qjson" "A library for processing and serializing JSON files" "http://qjson.sourceforge.net" FALSE "" "Required for compiling Ark's unit tests") + +########### next target ############### + +set(kerfuffle_SRCS + archive.cpp + archiveinterface.cpp + jobs.cpp + extractiondialog.cpp + adddialog.cpp + queries.cpp + addtoarchive.cpp + cliinterface.cpp + ) + +kde4_add_kcfg_files(kerfuffle_SRCS settings.kcfgc) + +kde4_add_ui_files(kerfuffle_SRCS extractiondialog.ui adddialog.ui ) + +kde4_add_library(kerfuffle SHARED ${kerfuffle_SRCS}) + +target_link_libraries(kerfuffle ${KDE4_KFILE_LIBS} ${KDE4_KPARTS_LIBS}) +if (NOT WIN32) + target_link_libraries(kerfuffle ${KDE4_KPTY_LIBS}) +endif (NOT WIN32) + +set_target_properties(kerfuffle PROPERTIES VERSION ${GENERIC_LIB_VERSION} SOVERSION ${GENERIC_LIB_SOVERSION}) + +install(TARGETS kerfuffle ${INSTALL_TARGETS_DEFAULT_ARGS}) + +install(FILES kerfufflePlugin.desktop DESTINATION ${SERVICETYPES_INSTALL_DIR}) +install(FILES ark.kcfg DESTINATION ${KCFG_INSTALL_DIR}) + +if (QJSON_FOUND) + # This is a hack to make QJSON work with both 0.7.1 (the latest stable) + # and the current master (b440550), which uses a different casing for + # the CMake variables. + # It should be removed when QJSON master becomes sane and reverts the + # casing again. + if (QJSON_LIBRARIES AND QJSON_INCLUDE_DIR) + set(KERFUFFLE_QJSON_LIBRARIES "${QJSON_LIBRARIES}") + set(KERFUFFLE_QJSON_INCLUDE_DIR "${QJSON_INCLUDE_DIR}") + else (QJSON_LIBRARIES AND QJSON_INCLUDE_DIR) + set(KERFUFFLE_QJSON_LIBRARIES "${qjson_LIBRARIES}") + set(KERFUFFLE_QJSON_INCLUDE_DIR "${qjson_INCLUDE_DIR}") + endif (QJSON_LIBRARIES AND QJSON_INCLUDE_DIR) + + add_subdirectory(tests) +endif (QJSON_FOUND) diff --git a/ark/kerfuffle/adddialog.cpp b/ark/kerfuffle/adddialog.cpp new file mode 100644 index 00000000..2950ee6e --- /dev/null +++ b/ark/kerfuffle/adddialog.cpp @@ -0,0 +1,135 @@ +/* + * ark -- archiver for the KDE project + * + * Copyright (C) 2008 Harald Hvaal + * Copyright (C) 2009,2011 Raphael Kubo da Costa + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ( INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION ) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "adddialog.h" +#include "ui_adddialog.h" +#include "kerfuffle/archive.h" + +#include +#include +#include + +#include +#include + +namespace Kerfuffle +{ +class AddDialogUI: public QWidget, public Ui::AddDialog +{ +public: + AddDialogUI(QWidget *parent = 0) + : QWidget(parent) { + setupUi(this); + } +}; + +AddDialog::AddDialog(const QStringList& itemsToAdd, + const KUrl & startDir, + const QString & filter, + QWidget * parent, + QWidget * widget + ) + : KFileDialog(startDir, filter, parent, widget) +{ + setOperationMode(KFileDialog::Saving); + setMode(KFile::File | KFile::LocalOnly); + setConfirmOverwrite(true); + setCaption(i18n("Compress to Archive")); + + loadConfiguration(); + + connect(this, SIGNAL(okClicked()), SLOT(updateDefaultMimeType())); + + m_ui = new AddDialogUI(this); + mainWidget()->layout()->addWidget(m_ui); + + setupIconList(itemsToAdd); + + // Set up a default name if there's only one file to compress + if (itemsToAdd.size() == 1) { + const QFileInfo fileInfo(itemsToAdd.first()); + const QString fileName = + fileInfo.isDir() ? fileInfo.dir().dirName() : fileInfo.baseName(); + + // #272914: Add an extension when it is present, otherwise KFileDialog + // will not automatically add it as baseFileName is a file which + // already exists. + setSelection(fileName + currentFilterMimeType()->mainExtension()); + } + + //These extra options will be implemented in a 4.2+ version of + //ark + m_ui->groupExtraOptions->hide(); +} + +void AddDialog::loadConfiguration() +{ + m_config = KConfigGroup(KGlobal::config()->group("AddDialog")); + + const QString defaultMimeType = QLatin1String( "application/x-compressed-tar" ); + const QStringList writeMimeTypes = Kerfuffle::supportedWriteMimeTypes(); + const QString lastMimeType = m_config.readEntry("LastMimeType", defaultMimeType); + + if (writeMimeTypes.contains(lastMimeType)) { + setMimeFilter(writeMimeTypes, lastMimeType); + } else { + setMimeFilter(writeMimeTypes, defaultMimeType); + } +} + +void AddDialog::setupIconList(const QStringList& itemsToAdd) +{ + QStandardItemModel* listModel = new QStandardItemModel(this); + QStringList sortedList(itemsToAdd); + + sortedList.sort(); + + Q_FOREACH(const QString& urlString, sortedList) { + KUrl url(urlString); + + QStandardItem* item = new QStandardItem; + item->setText(url.fileName()); + + QString iconName = KMimeType::iconNameForUrl(url); + item->setIcon(KIcon(iconName)); + + item->setData(QVariant(url), KFilePlacesModel::UrlRole); + + listModel->appendRow(item); + } + + m_ui->compressList->setModel(listModel); +} + +void AddDialog::updateDefaultMimeType() +{ + m_config.writeEntry("LastMimeType", currentMimeFilter()); +} +} + +#include "adddialog.moc" diff --git a/ark/kerfuffle/adddialog.h b/ark/kerfuffle/adddialog.h new file mode 100644 index 00000000..7c9f03e9 --- /dev/null +++ b/ark/kerfuffle/adddialog.h @@ -0,0 +1,63 @@ +/* + * ark -- archiver for the KDE project + * + * Copyright (C) 2008 Harald Hvaal + * Copyright (C) 2009 Raphael Kubo da Costa + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ( INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION ) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef ADDDIALOG_H +#define ADDDIALOG_H + +#include "kerfuffle_export.h" + +#include +#include + +namespace Kerfuffle +{ +class KERFUFFLE_EXPORT AddDialog : public KFileDialog +{ + Q_OBJECT + +public: + AddDialog(const QStringList & itemsToAdd, + const KUrl & startDir, + const QString & filter, + QWidget * parent, + QWidget * widget = 0 + ); + +private: + class AddDialogUI *m_ui; + KConfigGroup m_config; + + void loadConfiguration(); + void setupIconList(const QStringList& itemsToAdd); + +private slots: + void updateDefaultMimeType(); +}; +} + +#endif diff --git a/ark/kerfuffle/adddialog.ui b/ark/kerfuffle/adddialog.ui new file mode 100644 index 00000000..a1c595b8 --- /dev/null +++ b/ark/kerfuffle/adddialog.ui @@ -0,0 +1,113 @@ + + AddDialog + + + + 0 + 0 + 565 + 113 + + + + + 0 + 0 + + + + + + + + 0 + 0 + + + + Files/Folders to Compress + + + + + + + 0 + 0 + + + + false + + + QFrame::NoFrame + + + QFrame::Plain + + + 0 + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::NoSelection + + + + 32 + 32 + + + + Qt::ElideMiddle + + + QAbstractItemView::ScrollPerPixel + + + QListView::Adjust + + + QListView::IconMode + + + true + + + + + + + + + + + 0 + 0 + + + + Extra Compression Options + + + + + + Easter egg for the developers: +This is where future versions will have extra compression options for the various compression interfaces. + + + true + + + + + + + + + + + diff --git a/ark/kerfuffle/addtoarchive.cpp b/ark/kerfuffle/addtoarchive.cpp new file mode 100644 index 00000000..d13e0958 --- /dev/null +++ b/ark/kerfuffle/addtoarchive.cpp @@ -0,0 +1,213 @@ +/* + * ark -- archiver for the KDE project + * + * Copyright (C) 2008 Harald Hvaal + * Copyright (C) 2009 Raphael Kubo da Costa + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ( INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION ) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "addtoarchive.h" +#include "adddialog.h" +#include "archive.h" +#include "jobs.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace Kerfuffle +{ +AddToArchive::AddToArchive(QObject *parent) + : KJob(parent), m_changeToFirstPath(false) +{ +} + +AddToArchive::~AddToArchive() +{ +} + +void AddToArchive::setAutoFilenameSuffix(const QString& suffix) +{ + m_autoFilenameSuffix = suffix; +} + +void AddToArchive::setChangeToFirstPath(bool value) +{ + m_changeToFirstPath = value; +} + +void AddToArchive::setFilename(const KUrl& path) +{ + m_filename = path.pathOrUrl(); +} + +void AddToArchive::setMimeType(const QString & mimeType) +{ + m_mimeType = mimeType; +} + +bool AddToArchive::showAddDialog(void) +{ + QWeakPointer dialog = new Kerfuffle::AddDialog( + m_inputs, // itemsToAdd + KUrl(m_firstPath), // startDir + QLatin1String( "" ), // filter + NULL, // parent + NULL); // widget + + bool ret = dialog.data()->exec(); + + if (ret) { + kDebug() << "Returned URL:" << dialog.data()->selectedUrl(); + kDebug() << "Returned mime:" << dialog.data()->currentMimeFilter(); + setFilename(dialog.data()->selectedUrl()); + setMimeType(dialog.data()->currentMimeFilter()); + } + + delete dialog.data(); + + return ret; +} + +bool AddToArchive::addInput(const KUrl& url) +{ + m_inputs << url.pathOrUrl( + QFileInfo(url.pathOrUrl()).isDir() ? + KUrl::AddTrailingSlash : + KUrl::RemoveTrailingSlash + ); + + if (m_firstPath.isEmpty()) { + QString firstEntry = url.pathOrUrl(KUrl::RemoveTrailingSlash); + m_firstPath = QFileInfo(firstEntry).dir().absolutePath(); + } + + return true; +} + +void AddToArchive::start() +{ + QTimer::singleShot(0, this, SLOT(slotStartJob())); +} + +// TODO: If this class should ever be called outside main.cpp, +// the returns should be preceded by emitResult(). +void AddToArchive::slotStartJob(void) +{ + kDebug(); + + Kerfuffle::CompressionOptions options; + + if (!m_inputs.size()) { + KMessageBox::error(NULL, i18n("No input files were given.")); + return; + } + + Kerfuffle::Archive *archive; + if (!m_filename.isEmpty()) { + archive = Kerfuffle::Archive::create(m_filename, m_mimeType, this); + kDebug() << "Set filename to " << m_filename; + } else { + if (m_autoFilenameSuffix.isEmpty()) { + KMessageBox::error(NULL, i18n("You need to either supply a filename for the archive or a suffix (such as rar, tar.gz) with the --autofilename argument.")); + return; + } + + if (m_firstPath.isEmpty()) { + kDebug() << "Weird, this should not happen. no firstpath defined. aborting"; + return; + } + + QString base = QFileInfo(m_inputs.first()).absoluteFilePath(); + if (base.endsWith(QLatin1Char('/'))) { + base.chop(1); + } + + QString finalName = base + QLatin1Char( '.' ) + m_autoFilenameSuffix; + + //if file already exists, append a number to the base until it doesn't + //exist + int appendNumber = 0; + while (QFileInfo(finalName).exists()) { + ++appendNumber; + finalName = base + QLatin1Char( '_' ) + QString::number(appendNumber) + QLatin1Char( '.' ) + m_autoFilenameSuffix; + } + + kDebug() << "Autoset filename to "<< finalName; + archive = Kerfuffle::Archive::create(finalName, m_mimeType, this); + } + + if (archive == NULL) { + KMessageBox::error(NULL, i18n("Failed to create the new archive. Permissions might not be sufficient.")); + return; + } else if (archive->isReadOnly()) { + KMessageBox::error(NULL, i18n("It is not possible to create archives of this type.")); + return; + } + + if (m_changeToFirstPath) { + if (m_firstPath.isEmpty()) { + kDebug() << "Weird, this should not happen. no firstpath defined. aborting"; + return; + } + + const QDir stripDir(m_firstPath); + + for (int i = 0; i < m_inputs.size(); ++i) { + m_inputs[i] = stripDir.absoluteFilePath(m_inputs.at(i)); + } + + options[QLatin1String( "GlobalWorkDir" )] = stripDir.path(); + kDebug() << "Setting GlobalWorkDir to " << stripDir.path(); + } + + Kerfuffle::AddJob *job = + archive->addFiles(m_inputs, options); + + KIO::getJobTracker()->registerJob(job); + + connect(job, SIGNAL(result(KJob*)), + this, SLOT(slotFinished(KJob*))); + + job->start(); +} + +void AddToArchive::slotFinished(KJob *job) +{ + kDebug(); + + if (job->error()) { + KMessageBox::error(NULL, job->errorText()); + } + + emitResult(); +} +} diff --git a/ark/kerfuffle/addtoarchive.h b/ark/kerfuffle/addtoarchive.h new file mode 100644 index 00000000..fa4608e0 --- /dev/null +++ b/ark/kerfuffle/addtoarchive.h @@ -0,0 +1,85 @@ +/* + * ark -- archiver for the KDE project + * + * Copyright (C) 2008 Harald Hvaal + * Copyright (C) 2009 Raphael Kubo da Costa + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ( INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION ) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef ADDTOARCHIVE_H +#define ADDTOARCHIVE_H + +#include "kerfuffle_export.h" + +#include +#include + +/** + * Compresses all input files into an archive. + * + * This is a job class that creates a compressed archive + * with all the given input files. + * + * It provides the functionality for the --add command-line + * option, and does not need the GUI to be running. + * + * @author Harald Hvaal + */ +namespace Kerfuffle +{ +class KERFUFFLE_EXPORT AddToArchive : public KJob +{ + Q_OBJECT + +public: + AddToArchive(QObject *parent = 0); + ~AddToArchive(); + + bool showAddDialog(); + void setPreservePaths(bool value); + void setChangeToFirstPath(bool value); + +public slots: + bool addInput(const KUrl& url); + void setAutoFilenameSuffix(const QString& suffix); + void setFilename(const KUrl& path); + void setMimeType(const QString & mimeType); + void start(); + +private slots: + void slotFinished(KJob*); + void slotStartJob(); + +private: + QString m_filename; + QString m_strippedPath; + QString m_autoFilenameSuffix; + QString m_firstPath; + QString m_mimeType; + QStringList m_inputs; + bool m_changeToFirstPath; +}; +} + +#endif // ADDTOARCHIVE_H + diff --git a/ark/kerfuffle/archive.cpp b/ark/kerfuffle/archive.cpp new file mode 100644 index 00000000..c2c5b185 --- /dev/null +++ b/ark/kerfuffle/archive.cpp @@ -0,0 +1,333 @@ +/* + * Copyright (c) 2007 Henrique Pinto + * Copyright (c) 2008 Harald Hvaal + * Copyright (c) 2009-2011 Raphael Kubo da Costa + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ( INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION ) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive.h" +#include "archiveinterface.h" +#include "jobs.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +static bool comparePlugins(const KService::Ptr &p1, const KService::Ptr &p2) +{ + return (p1->property(QLatin1String( "X-KDE-Priority" )).toInt()) > (p2->property(QLatin1String( "X-KDE-Priority" )).toInt()); +} + +static QString determineMimeType(const QString& filename) +{ + if (!QFile::exists(filename)) { + return KMimeType::findByPath(filename)->name(); + } + + QFile file(filename); + if (!file.open(QIODevice::ReadOnly)) { + return QString(); + } + + const qint64 maxSize = 0x100000; // 1MB + const qint64 bufferSize = qMin(maxSize, file.size()); + const QByteArray buffer = file.read(bufferSize); + + return KMimeType::findByNameAndContent(filename, buffer)->name(); +} + +static KService::List findPluginOffers(const QString& filename, const QString& fixedMimeType) +{ + KService::List offers; + + const QString mimeType = fixedMimeType.isEmpty() ? determineMimeType(filename) : fixedMimeType; + + if (!mimeType.isEmpty()) { + offers = KMimeTypeTrader::self()->query(mimeType, QLatin1String( "Kerfuffle/Plugin" ), QLatin1String( "(exist Library)" )); + qSort(offers.begin(), offers.end(), comparePlugins); + } + + return offers; +} + +namespace Kerfuffle +{ + +Archive *Archive::create(const QString &fileName, QObject *parent) +{ + return create(fileName, QString(), parent); +} + +Archive *Archive::create(const QString &fileName, const QString &fixedMimeType, QObject *parent) +{ + qRegisterMetaType("ArchiveEntry"); + + const KService::List offers = findPluginOffers(fileName, fixedMimeType); + + if (offers.isEmpty()) { + kDebug() << "Could not find a plugin to handle" << fileName; + return NULL; + } + + const QString pluginName = offers.first()->library(); + kDebug() << "Loading plugin" << pluginName; + + KPluginFactory * const factory = KPluginLoader(pluginName).factory(); + if (!factory) { + kDebug() << "Invalid plugin factory for" << pluginName; + return NULL; + } + + QVariantList args; + args.append(QVariant(QFileInfo(fileName).absoluteFilePath())); + + ReadOnlyArchiveInterface * const iface = factory->create(0, args); + if (!iface) { + kDebug() << "Could not create plugin instance" << pluginName << "for" << fileName; + return NULL; + } + + return new Archive(iface, parent); +} + +Archive::Archive(ReadOnlyArchiveInterface *archiveInterface, QObject *parent) + : QObject(parent), + m_iface(archiveInterface), + m_hasBeenListed(false), + m_isPasswordProtected(false), + m_isSingleFolderArchive(false) +{ + Q_ASSERT(archiveInterface); + archiveInterface->setParent(this); +} + +Archive::~Archive() +{ +} + +bool Archive::isReadOnly() const +{ + return m_iface->isReadOnly(); +} + +KJob* Archive::open() +{ + return 0; +} + +KJob* Archive::create() +{ + return 0; +} + +ListJob* Archive::list() +{ + ListJob *job = new ListJob(m_iface, this); + job->setAutoDelete(false); + + //if this job has not been listed before, we grab the opportunity to + //collect some information about the archive + if (!m_hasBeenListed) { + connect(job, SIGNAL(result(KJob*)), + this, SLOT(onListFinished(KJob*))); + } + return job; +} + +DeleteJob* Archive::deleteFiles(const QList & files) +{ + if (m_iface->isReadOnly()) { + return 0; + } + DeleteJob *newJob = new DeleteJob(files, static_cast(m_iface), this); + + return newJob; +} + +AddJob* Archive::addFiles(const QStringList & files, const CompressionOptions& options) +{ + Q_ASSERT(!m_iface->isReadOnly()); + AddJob *newJob = new AddJob(files, options, static_cast(m_iface), this); + connect(newJob, SIGNAL(result(KJob*)), + this, SLOT(onAddFinished(KJob*))); + return newJob; +} + +ExtractJob* Archive::copyFiles(const QList & files, const QString & destinationDir, ExtractionOptions options) +{ + ExtractionOptions newOptions = options; + if (isPasswordProtected()) { + newOptions[QLatin1String( "PasswordProtectedHint" )] = true; + } + + ExtractJob *newJob = new ExtractJob(files, destinationDir, newOptions, m_iface, this); + return newJob; +} + +QString Archive::fileName() const +{ + return m_iface->filename(); +} + +void Archive::onAddFinished(KJob* job) +{ + //if the archive was previously a single folder archive and an add job + //has successfully finished, then it is no longer a single folder + //archive (for the current implementation, which does not allow adding + //folders/files other places than the root. + //TODO: handle the case of creating a new file and singlefolderarchive + //then. + if (m_isSingleFolderArchive && !job->error()) { + m_isSingleFolderArchive = false; + } +} + +void Archive::onListFinished(KJob* job) +{ + ListJob *ljob = qobject_cast(job); + m_extractedFilesSize = ljob->extractedFilesSize(); + m_isSingleFolderArchive = ljob->isSingleFolderArchive(); + m_isPasswordProtected = ljob->isPasswordProtected(); + m_subfolderName = ljob->subfolderName(); + if (m_subfolderName.isEmpty()) { + QFileInfo fi(fileName()); + QString base = fi.completeBaseName(); + + //special case for tar.gz/bzip2 files + if (base.right(4).toUpper() == QLatin1String(".TAR")) { + base.chop(4); + } + + m_subfolderName = base; + } + + m_hasBeenListed = true; +} + +void Archive::listIfNotListed() +{ + if (!m_hasBeenListed) { + KJob *job = list(); + + connect(job, SIGNAL(userQuery(Kerfuffle::Query*)), + SLOT(onUserQuery(Kerfuffle::Query*))); + + QEventLoop loop(this); + + connect(job, SIGNAL(result(KJob*)), + &loop, SLOT(quit())); + job->start(); + loop.exec(); // krazy:exclude=crashy + } +} + +void Archive::onUserQuery(Query* query) +{ + query->execute(); +} + +bool Archive::isSingleFolderArchive() +{ + listIfNotListed(); + return m_isSingleFolderArchive; +} + +bool Archive::isPasswordProtected() +{ + listIfNotListed(); + return m_isPasswordProtected; +} + +QString Archive::subfolderName() +{ + listIfNotListed(); + return m_subfolderName; +} + +void Archive::setPassword(const QString &password) +{ + m_iface->setPassword(password); +} + +QStringList supportedMimeTypes() +{ + const QLatin1String constraint("(exist Library)"); + const QLatin1String basePartService("Kerfuffle/Plugin"); + + const KService::List offers = KServiceTypeTrader::self()->query(basePartService, constraint); + KService::List::ConstIterator it = offers.constBegin(); + KService::List::ConstIterator itEnd = offers.constEnd(); + + QStringList supported; + + for (; it != itEnd; ++it) { + KService::Ptr service = *it; + QStringList mimeTypes = service->serviceTypes(); + + foreach (const QString& mimeType, mimeTypes) { + if (mimeType != basePartService && !supported.contains(mimeType)) { + supported.append(mimeType); + } + } + } + + kDebug() << "Returning" << supported; + + return supported; +} + +QStringList supportedWriteMimeTypes() +{ + const QLatin1String constraint("(exist Library) and ([X-KDE-Kerfuffle-ReadWrite] == true)"); + const QLatin1String basePartService("Kerfuffle/Plugin"); + + const KService::List offers = KServiceTypeTrader::self()->query(basePartService, constraint); + KService::List::ConstIterator it = offers.constBegin(); + KService::List::ConstIterator itEnd = offers.constEnd(); + + QStringList supported; + + for (; it != itEnd; ++it) { + KService::Ptr service = *it; + QStringList mimeTypes = service->serviceTypes(); + + foreach (const QString& mimeType, mimeTypes) { + if (mimeType != basePartService && !supported.contains(mimeType)) { + supported.append(mimeType); + } + } + } + + kDebug() << "Returning" << supported; + + return supported; +} + +} // namespace Kerfuffle diff --git a/ark/kerfuffle/archive.h b/ark/kerfuffle/archive.h new file mode 100644 index 00000000..515788f9 --- /dev/null +++ b/ark/kerfuffle/archive.h @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2007 Henrique Pinto + * Copyright (c) 2008 Harald Hvaal + * Copyright (c) 2011 Raphael Kubo da Costa + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ( INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION ) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef ARCHIVE_H +#define ARCHIVE_H + +#include "kerfuffle_export.h" + +#include +#include +#include + +class KJob; + +namespace Kerfuffle +{ +class ListJob; +class ExtractJob; +class DeleteJob; +class AddJob; +class Query; +class ReadOnlyArchiveInterface; + +/** + * Meta data related to one entry in a compressed archive. + * + * When creating a plugin, information about every single entry in + * an archive is contained in an ArchiveEntry, and metadata + * is set with the entries in this enum. + * + * Please notice that not all archive formats support all the properties + * below, so set those that are available. + */ +enum EntryMetaDataType { + FileName = 0, /**< The entry's file name */ + InternalID, /**< The entry's ID for Ark's internal manipulation */ + Permissions, /**< The entry's permissions */ + Owner, /**< The user the entry belongs to */ + Group, /**< The user group the entry belongs to */ + Size, /**< The entry's original size */ + CompressedSize, /**< The compressed size for the entry */ + Link, /**< The entry is a symbolic link */ + Ratio, /**< The compression ratio for the entry */ + CRC, /**< The entry's CRC */ + Method, /**< The compression method used on the entry */ + Version, /**< The archiver version needed to extract the entry */ + Timestamp, /**< The timestamp for the current entry */ + IsDirectory, /**< The entry is a directory */ + Comment, + IsPasswordProtected, /**< The entry is password-protected */ + Custom = 1048576 +}; + +typedef QHash ArchiveEntry; + +/** +These are the extra options for doing the compression. Naming convention +is CamelCase with either Global, or the compression type (such as Zip, +Rar, etc), followed by the property name used + */ +typedef QHash CompressionOptions; +typedef QHash ExtractionOptions; + +class KERFUFFLE_EXPORT Archive : public QObject +{ + Q_OBJECT + +public: + static Archive *create(const QString &fileName, QObject *parent = 0); + static Archive *create(const QString &fileName, const QString &fixedMimeType, QObject *parent = 0); + ~Archive(); + + QString fileName() const; + bool isReadOnly() const; + + KJob* open(); + KJob* create(); + ListJob* list(); + DeleteJob* deleteFiles(const QList & files); + + /** + * Compression options that should be handled by all interfaces: + * + * GlobalWorkDir - Change to this dir before adding the new files. + * The path names should then be added relative to this directory. + * + * TODO: find a way to actually add files to specific locations in + * the archive + * (not supported yet) GlobalPathInArchive - a path relative to the + * archive root where the files will be added under + * + */ + AddJob* addFiles(const QStringList & files, const CompressionOptions& options = CompressionOptions()); + + ExtractJob* copyFiles(const QList & files, const QString & destinationDir, ExtractionOptions options = ExtractionOptions()); + + bool isSingleFolderArchive(); + QString subfolderName(); + bool isPasswordProtected(); + + void setPassword(const QString &password); + +private slots: + void onListFinished(KJob*); + void onAddFinished(KJob*); + void onUserQuery(Kerfuffle::Query*); + +private: + Archive(ReadOnlyArchiveInterface *archiveInterface, QObject *parent = 0); + + void listIfNotListed(); + ReadOnlyArchiveInterface *m_iface; + bool m_hasBeenListed; + bool m_isPasswordProtected; + bool m_isSingleFolderArchive; + QString m_subfolderName; + qlonglong m_extractedFilesSize; +}; + +KERFUFFLE_EXPORT QStringList supportedMimeTypes(); +KERFUFFLE_EXPORT QStringList supportedWriteMimeTypes(); +} // namespace Kerfuffle + + +#endif // ARCHIVE_H diff --git a/ark/kerfuffle/archiveinterface.cpp b/ark/kerfuffle/archiveinterface.cpp new file mode 100644 index 00000000..3319de07 --- /dev/null +++ b/ark/kerfuffle/archiveinterface.cpp @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2007 Henrique Pinto + * Copyright (c) 2008-2009 Harald Hvaal + * Copyright (c) 2009-2012 Raphael Kubo da Costa + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ( INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION ) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archiveinterface.h" +#include +#include + +#include +#include + +namespace Kerfuffle +{ +ReadOnlyArchiveInterface::ReadOnlyArchiveInterface(QObject *parent, const QVariantList & args) + : QObject(parent), m_waitForFinishedSignal(false) +{ + kDebug(); + m_filename = args.first().toString(); +} + +ReadOnlyArchiveInterface::~ReadOnlyArchiveInterface() +{ +} + +QString ReadOnlyArchiveInterface::filename() const +{ + return m_filename; +} + +bool ReadOnlyArchiveInterface::isReadOnly() const +{ + return true; +} + +bool ReadOnlyArchiveInterface::open() +{ + return true; +} + +void ReadOnlyArchiveInterface::setPassword(const QString &password) +{ + m_password = password; +} + +QString ReadOnlyArchiveInterface::password() const +{ + return m_password; +} + +bool ReadOnlyArchiveInterface::doKill() +{ + //default implementation + return false; +} + +bool ReadOnlyArchiveInterface::doSuspend() +{ + //default implementation + return false; +} + +bool ReadOnlyArchiveInterface::doResume() +{ + //default implementation + return false; +} + +ReadWriteArchiveInterface::ReadWriteArchiveInterface(QObject *parent, const QVariantList & args) + : ReadOnlyArchiveInterface(parent, args) +{ +} + +ReadWriteArchiveInterface::~ReadWriteArchiveInterface() +{ +} + +bool ReadOnlyArchiveInterface::waitForFinishedSignal() +{ + return m_waitForFinishedSignal; +} + +void ReadOnlyArchiveInterface::setWaitForFinishedSignal(bool value) +{ + m_waitForFinishedSignal = value; +} + +bool ReadWriteArchiveInterface::isReadOnly() const +{ + QFileInfo fileInfo(filename()); + if (fileInfo.exists()) { + return ! fileInfo.isWritable(); + } else { + return !fileInfo.dir().exists(); // TODO: Should also check if we can create a file in that directory + } +} + +} // namespace Kerfuffle + +#include "archiveinterface.moc" diff --git a/ark/kerfuffle/archiveinterface.h b/ark/kerfuffle/archiveinterface.h new file mode 100644 index 00000000..801318b7 --- /dev/null +++ b/ark/kerfuffle/archiveinterface.h @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2007 Henrique Pinto + * Copyright (c) 2008-2009 Harald Hvaal + * Copyright (c) 2009-2012 Raphael Kubo da Costa + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ( INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION ) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef ARCHIVEINTERFACE_H +#define ARCHIVEINTERFACE_H + +#include "archive.h" +#include "kerfuffle_export.h" + +#include +#include +#include +#include + +namespace Kerfuffle +{ +class Query; + +class KERFUFFLE_EXPORT ReadOnlyArchiveInterface: public QObject +{ + Q_OBJECT +public: + explicit ReadOnlyArchiveInterface(QObject *parent, const QVariantList & args); + virtual ~ReadOnlyArchiveInterface(); + + /** + * Returns the filename of the archive currently being handled. + */ + QString filename() const; + + /** + * Returns whether the file can only be read. + * + * @return @c true The file cannot be written. + * @return @c false The file can be read and written. + */ + virtual bool isReadOnly() const; + + virtual bool open(); + + /** + * List archive contents. + * This runs the process of reading archive contents. + * When subclassing, you can block as long as you need, the function runs + * in its own thread. + * @returns whether the listing succeeded. + * @note If returning false, make sure to emit the error() signal beforewards to notify + * the user of the error condition. + */ + virtual bool list() = 0; + void setPassword(const QString &password); + + /** + * Extract files from archive. + * Globally recognized extraction options: + * @li PreservePaths - preserve file paths (extract flat if false) + * @li RootNode - node in the archive which will correspond to the @arg destinationDirectory + * When subclassing, you can block as long as you need, the function runs + * in its own thread. + * @returns whether the listing succeeded. + * @note If returning false, make sure to emit the error() signal beforewards to notify + * the user of the error condition. + */ + virtual bool copyFiles(const QList & files, const QString & destinationDirectory, ExtractionOptions options) = 0; + + bool waitForFinishedSignal(); + + virtual bool doKill(); + virtual bool doSuspend(); + virtual bool doResume(); + +signals: + void error(const QString &message, const QString &details = QString()); + void entry(const ArchiveEntry &archiveEntry); + void entryRemoved(const QString &path); + void progress(double progress); + void info(const QString &info); + void finished(bool result); + void userQuery(Query *query); + +protected: + QString password() const; + /** + * Setting this option to true will not exit the thread with the + * exit of the various functions, but rather when finished(bool) is + * called. Doing this one can use the event loop easily while doing + * the operation. + */ + void setWaitForFinishedSignal(bool value); + +private: + QString m_filename; + QString m_password; + bool m_waitForFinishedSignal; +}; + +class KERFUFFLE_EXPORT ReadWriteArchiveInterface: public ReadOnlyArchiveInterface +{ + Q_OBJECT +public: + explicit ReadWriteArchiveInterface(QObject *parent, const QVariantList & args); + virtual ~ReadWriteArchiveInterface(); + + virtual bool isReadOnly() const; + + //see archive.h for a list of what the compressionoptions might + //contain + virtual bool addFiles(const QStringList & files, const CompressionOptions& options) = 0; + virtual bool deleteFiles(const QList & files) = 0; +}; + +} // namespace Kerfuffle + +#endif // ARCHIVEINTERFACE_H diff --git a/ark/kerfuffle/ark.kcfg b/ark/kerfuffle/ark.kcfg new file mode 100644 index 00000000..97d20866 --- /dev/null +++ b/ark/kerfuffle/ark.kcfg @@ -0,0 +1,25 @@ + + + + + + + false + + + + false + + + + true + + + + + + + diff --git a/ark/kerfuffle/cliinterface.cpp b/ark/kerfuffle/cliinterface.cpp new file mode 100644 index 00000000..eab1c6a3 --- /dev/null +++ b/ark/kerfuffle/cliinterface.cpp @@ -0,0 +1,751 @@ +/* + * ark -- archiver for the KDE project + * + * Copyright (C) 2009 Harald Hvaal + * Copyright (C) 2009-2011 Raphael Kubo da Costa + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ( INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION ) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "cliinterface.h" +#include "queries.h" + +#ifdef Q_OS_WIN +# include +#else +# include +# include +#endif + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Kerfuffle +{ +CliInterface::CliInterface(QObject *parent, const QVariantList & args) + : ReadWriteArchiveInterface(parent, args), + m_process(0), + m_listEmptyLines(false), + m_abortingOperation(false) +{ + //because this interface uses the event loop + setWaitForFinishedSignal(true); + + if (QMetaType::type("QProcess::ExitStatus") == 0) { + qRegisterMetaType("QProcess::ExitStatus"); + } +} + +void CliInterface::cacheParameterList() +{ + m_param = parameterList(); + Q_ASSERT(m_param.contains(ExtractProgram)); + Q_ASSERT(m_param.contains(ListProgram)); + Q_ASSERT(m_param.contains(PreservePathSwitch)); + Q_ASSERT(m_param.contains(FileExistsExpression)); + Q_ASSERT(m_param.contains(FileExistsInput)); +} + +CliInterface::~CliInterface() +{ + Q_ASSERT(!m_process); +} + +void CliInterface::setListEmptyLines(bool emptyLines) +{ + m_listEmptyLines = emptyLines; +} + +bool CliInterface::list() +{ + cacheParameterList(); + m_operationMode = List; + + QStringList args = m_param.value(ListArgs).toStringList(); + substituteListVariables(args); + + if (!runProcess(m_param.value(ListProgram).toStringList(), args)) { + failOperation(); + return false; + } + + return true; +} + +bool CliInterface::copyFiles(const QList & files, const QString & destinationDirectory, ExtractionOptions options) +{ + kDebug(); + cacheParameterList(); + + m_operationMode = Copy; + + //start preparing the argument list + QStringList args = m_param.value(ExtractArgs).toStringList(); + + //now replace the various elements in the list + for (int i = 0; i < args.size(); ++i) { + QString argument = args.at(i); + kDebug() << "Processing argument " << argument; + + if (argument == QLatin1String( "$Archive" )) { + args[i] = filename(); + } + + if (argument == QLatin1String( "$PreservePathSwitch" )) { + QStringList replacementFlags = m_param.value(PreservePathSwitch).toStringList(); + Q_ASSERT(replacementFlags.size() == 2); + + bool preservePaths = options.value(QLatin1String( "PreservePaths" )).toBool(); + QString theReplacement; + if (preservePaths) { + theReplacement = replacementFlags.at(0); + } else { + theReplacement = replacementFlags.at(1); + } + + if (theReplacement.isEmpty()) { + args.removeAt(i); + --i; //decrement to compensate for the variable we removed + } else { + //but in this case we don't have to decrement, we just + //replace it + args[i] = theReplacement; + } + } + + if (argument == QLatin1String( "$PasswordSwitch" )) { + //if the PasswordSwitch argument has been added, we at least + //assume that the format of the switch has been added as well + Q_ASSERT(m_param.contains(PasswordSwitch)); + + //we will decrement i afterwards + args.removeAt(i); + + //if we get a hint about this being a password protected archive, ask about + //the password in advance. + if ((options.value(QLatin1String("PasswordProtectedHint")).toBool()) && + (password().isEmpty())) { + kDebug() << "Password hint enabled, querying user"; + + Kerfuffle::PasswordNeededQuery query(filename()); + emit userQuery(&query); + query.waitForResponse(); + + if (query.responseCancelled()) { + failOperation(); + return false; + } + setPassword(query.password()); + } + + QString pass = password(); + + if (!pass.isEmpty()) { + QStringList theSwitch = m_param.value(PasswordSwitch).toStringList(); + for (int j = 0; j < theSwitch.size(); ++j) { + //get the argument part + QString newArg = theSwitch.at(j); + + //substitute the $Path + newArg.replace(QLatin1String( "$Password" ), pass); + + //put it in the arg list + args.insert(i + j, newArg); + ++i; + + } + } + --i; //decrement to compensate for the variable we replaced + } + + if (argument == QLatin1String( "$RootNodeSwitch" )) { + //if the RootNodeSwitch argument has been added, we at least + //assume that the format of the switch has been added as well + Q_ASSERT(m_param.contains(RootNodeSwitch)); + + //we will decrement i afterwards + args.removeAt(i); + + QString rootNode; + if (options.contains(QLatin1String( "RootNode" ))) { + rootNode = options.value(QLatin1String( "RootNode" )).toString(); + kDebug() << "Set root node " << rootNode; + } + + if (!rootNode.isEmpty()) { + QStringList theSwitch = m_param.value(RootNodeSwitch).toStringList(); + for (int j = 0; j < theSwitch.size(); ++j) { + //get the argument part + QString newArg = theSwitch.at(j); + + //substitute the $Path + newArg.replace(QLatin1String( "$Path" ), rootNode); + + //put it in the arg list + args.insert(i + j, newArg); + ++i; + + } + } + --i; //decrement to compensate for the variable we replaced + } + + if (argument == QLatin1String( "$Files" )) { + args.removeAt(i); + for (int j = 0; j < files.count(); ++j) { + args.insert(i + j, escapeFileName(files.at(j).toString())); + ++i; + } + --i; + } + } + + kDebug() << "Setting current dir to " << destinationDirectory; + QDir::setCurrent(destinationDirectory); + + if (!runProcess(m_param.value(ExtractProgram).toStringList(), args)) { + failOperation(); + return false; + } + + return true; +} + +bool CliInterface::addFiles(const QStringList & files, const CompressionOptions& options) +{ + cacheParameterList(); + + m_operationMode = Add; + + const QString globalWorkDir = options.value(QLatin1String( "GlobalWorkDir" )).toString(); + const QDir workDir = globalWorkDir.isEmpty() ? QDir::current() : QDir(globalWorkDir); + if (!globalWorkDir.isEmpty()) { + kDebug() << "GlobalWorkDir is set, changing dir to " << globalWorkDir; + QDir::setCurrent(globalWorkDir); + } + + //start preparing the argument list + QStringList args = m_param.value(AddArgs).toStringList(); + + //now replace the various elements in the list + for (int i = 0; i < args.size(); ++i) { + const QString argument = args.at(i); + kDebug() << "Processing argument " << argument; + + if (argument == QLatin1String( "$Archive" )) { + args[i] = filename(); + } + + if (argument == QLatin1String( "$Files" )) { + args.removeAt(i); + for (int j = 0; j < files.count(); ++j) { + // #191821: workDir must be used instead of QDir::current() + // so that symlinks aren't resolved automatically + // TODO: this kind of call should be moved upwards in the + // class hierarchy to avoid code duplication + const QString relativeName = + workDir.relativeFilePath(files.at(j)); + + args.insert(i + j, relativeName); + ++i; + } + --i; + } + } + + if (!runProcess(m_param.value(AddProgram).toStringList(), args)) { + failOperation(); + return false; + } + + return true; +} + +bool CliInterface::deleteFiles(const QList & files) +{ + cacheParameterList(); + m_operationMode = Delete; + + //start preparing the argument list + QStringList args = m_param.value(DeleteArgs).toStringList(); + + //now replace the various elements in the list + for (int i = 0; i < args.size(); ++i) { + QString argument = args.at(i); + kDebug() << "Processing argument " << argument; + + if (argument == QLatin1String( "$Archive" )) { + args[i] = filename(); + } else if (argument == QLatin1String( "$Files" )) { + args.removeAt(i); + for (int j = 0; j < files.count(); ++j) { + args.insert(i + j, escapeFileName(files.at(j).toString())); + ++i; + } + --i; + } + } + + m_removedFiles = files; + + if (!runProcess(m_param.value(DeleteProgram).toStringList(), args)) { + failOperation(); + return false; + } + + return true; +} + +bool CliInterface::runProcess(const QStringList& programNames, const QStringList& arguments) +{ + QString programPath; + for (int i = 0; i < programNames.count(); i++) { + programPath = KStandardDirs::findExe(programNames.at(i)); + if (!programPath.isEmpty()) + break; + } + if (programPath.isEmpty()) { + const QString names = programNames.join(QLatin1String(", ")); + emit error(i18ncp("@info", "Failed to locate program %2 on disk.", + "Failed to locate programs %2 on disk.", programNames.count(), names)); + emit finished(false); + return false; + } + + kDebug() << "Executing" << programPath << arguments; + + if (m_process) { + m_process->waitForFinished(); + delete m_process; + } + +#ifdef Q_OS_WIN + m_process = new KProcess; +#else + m_process = new KPtyProcess; + m_process->setPtyChannels(KPtyProcess::StdinChannel); + QEventLoop loop; + connect(m_process, SIGNAL(finished(int,QProcess::ExitStatus)), &loop, SLOT(quit()), Qt::DirectConnection); +#endif + + m_process->setOutputChannelMode(KProcess::MergedChannels); + m_process->setNextOpenMode(QIODevice::ReadWrite | QIODevice::Unbuffered | QIODevice::Text); + m_process->setProgram(programPath, arguments); + + connect(m_process, SIGNAL(readyReadStandardOutput()), SLOT(readStdout()), Qt::DirectConnection); + connect(m_process, SIGNAL(finished(int,QProcess::ExitStatus)), SLOT(processFinished(int,QProcess::ExitStatus)), Qt::DirectConnection); + + m_stdOutData.clear(); + + m_process->start(); + +#ifdef Q_OS_WIN + bool ret = m_process->waitForFinished(-1); +#else + bool ret = (loop.exec(QEventLoop::WaitForMoreEvents | QEventLoop::ExcludeUserInputEvents) == 0); +#endif + + Q_ASSERT(!m_process); + + return ret; +} + +void CliInterface::processFinished(int exitCode, QProcess::ExitStatus exitStatus) +{ + kDebug() << exitCode << exitStatus; + + //if the m_process pointer is gone, then there is nothing to worry + //about here + if (!m_process) { + return; + } + + if (m_operationMode == Delete) { + foreach(const QVariant& v, m_removedFiles) { + emit entryRemoved(v.toString()); + } + } + + //handle all the remaining data in the process + readStdout(true); + + delete m_process; + m_process = 0; + + emit progress(1.0); + + if (m_operationMode == Add) { + list(); + return; + } + + //and we're finished + emit finished(true); +} + +void CliInterface::failOperation() +{ + // TODO: Would be good to unit test #304764/#304178. + kDebug(); + doKill(); +} + +void CliInterface::readStdout(bool handleAll) +{ + //when hacking this function, please remember the following: + //- standard output comes in unpredictable chunks, this is why + //you can never know if the last part of the output is a complete line or not + //- console applications are not really consistent about what + //characters they send out (newline, backspace, carriage return, + //etc), so keep in mind that this function is supposed to handle + //all those special cases and be the lowest common denominator + + if (m_abortingOperation) + return; + + Q_ASSERT(m_process); + + if (!m_process->bytesAvailable()) { + //if process has no more data, we can just bail out + return; + } + + //if the process is still not finished (m_process is appearantly not + //set to NULL if here), then the operation should definitely not be in + //the main thread as this would freeze everything. assert this. + Q_ASSERT(QThread::currentThread() != QApplication::instance()->thread()); + + QByteArray dd = m_process->readAllStandardOutput(); + m_stdOutData += dd; + + QList lines = m_stdOutData.split('\n'); + + //The reason for this check is that archivers often do not end + //queries (such as file exists, wrong password) on a new line, but + //freeze waiting for input. So we check for errors on the last line in + //all cases. + // TODO: QLatin1String() might not be the best choice here. + // The call to handleLine() at the end of the method uses + // QString::fromLocal8Bit(), for example. + // TODO: The same check methods are called in handleLine(), this + // is suboptimal. + bool foundErrorMessage = + (checkForErrorMessage(QLatin1String( lines.last() ), WrongPasswordPatterns) || + checkForErrorMessage(QLatin1String( lines.last() ), ExtractionFailedPatterns) || + checkForPasswordPromptMessage(QLatin1String(lines.last())) || + checkForFileExistsMessage(QLatin1String( lines.last() ))); + + if (foundErrorMessage) { + handleAll = true; + } + + //this is complex, here's an explanation: + //if there is no newline, then there is no guaranteed full line to + //handle in the output. The exception is that it is supposed to handle + //all the data, OR if there's been an error message found in the + //partial data. + if (lines.size() == 1 && !handleAll) { + return; + } + + if (handleAll) { + m_stdOutData.clear(); + } else { + //because the last line might be incomplete we leave it for now + //note, this last line may be an empty string if the stdoutdata ends + //with a newline + m_stdOutData = lines.takeLast(); + } + + foreach(const QByteArray& line, lines) { + if (!line.isEmpty() || (m_listEmptyLines && m_operationMode == List)) { + handleLine(QString::fromLocal8Bit(line)); + } + } +} + +void CliInterface::handleLine(const QString& line) +{ + // TODO: This should be implemented by each plugin; the way progress is + // shown by each CLI application is subject to a lot of variation. + if ((m_operationMode == Copy || m_operationMode == Add) && m_param.contains(CaptureProgress) && m_param.value(CaptureProgress).toBool()) { + //read the percentage + int pos = line.indexOf(QLatin1Char( '%' )); + if (pos != -1 && pos > 1) { + int percentage = line.mid(pos - 2, 2).toInt(); + emit progress(float(percentage) / 100); + return; + } + } + + if (m_operationMode == Copy) { + if (checkForPasswordPromptMessage(line)) { + kDebug() << "Found a password prompt"; + + Kerfuffle::PasswordNeededQuery query(filename()); + emit userQuery(&query); + query.waitForResponse(); + + if (query.responseCancelled()) { + failOperation(); + return; + } + + setPassword(query.password()); + + const QString response(password() + QLatin1Char('\n')); + writeToProcess(response.toLocal8Bit()); + + return; + } + + if (checkForErrorMessage(line, WrongPasswordPatterns)) { + kDebug() << "Wrong password!"; + emit error(i18n("Incorrect password.")); + failOperation(); + return; + } + + if (checkForErrorMessage(line, ExtractionFailedPatterns)) { + kDebug() << "Error in extraction!!"; + emit error(i18n("Extraction failed because of an unexpected error.")); + failOperation(); + return; + } + + if (handleFileExistsMessage(line)) { + return; + } + } + + if (m_operationMode == List) { + if (checkForPasswordPromptMessage(line)) { + kDebug() << "Found a password prompt"; + + Kerfuffle::PasswordNeededQuery query(filename()); + emit userQuery(&query); + query.waitForResponse(); + + if (query.responseCancelled()) { + failOperation(); + return; + } + + setPassword(query.password()); + + const QString response(password() + QLatin1Char('\n')); + writeToProcess(response.toLocal8Bit()); + + return; + } + + if (checkForErrorMessage(line, WrongPasswordPatterns)) { + kDebug() << "Wrong password!"; + emit error(i18n("Incorrect password.")); + failOperation(); + return; + } + + if (checkForErrorMessage(line, ExtractionFailedPatterns)) { + kDebug() << "Error in extraction!!"; + emit error(i18n("Extraction failed because of an unexpected error.")); + failOperation(); + return; + } + + if (handleFileExistsMessage(line)) { + return; + } + + readListLine(line); + return; + } +} + +bool CliInterface::checkForPasswordPromptMessage(const QString& line) +{ + const QString passwordPromptPattern(m_param.value(PasswordPromptPattern).toString()); + + if (passwordPromptPattern.isEmpty()) + return false; + + if (m_passwordPromptPattern.isEmpty()) { + m_passwordPromptPattern.setPattern(m_param.value(PasswordPromptPattern).toString()); + } + + if (m_passwordPromptPattern.indexIn(line) != -1) { + return true; + } + + return false; +} + +bool CliInterface::checkForFileExistsMessage(const QString& line) +{ + if (m_existsPattern.isEmpty()) { + m_existsPattern.setPattern(m_param.value(FileExistsExpression).toString()); + } + if (m_existsPattern.indexIn(line) != -1) { + kDebug() << "Detected file existing!! Filename " << m_existsPattern.cap(1); + return true; + } + + return false; +} + +bool CliInterface::handleFileExistsMessage(const QString& line) +{ + if (!checkForFileExistsMessage(line)) { + return false; + } + + const QString filename = m_existsPattern.cap(1); + + Kerfuffle::OverwriteQuery query(QDir::current().path() + QLatin1Char( '/' ) + filename); + query.setNoRenameMode(true); + emit userQuery(&query); + kDebug() << "Waiting response"; + query.waitForResponse(); + + kDebug() << "Finished response"; + + QString responseToProcess; + const QStringList choices = m_param.value(FileExistsInput).toStringList(); + + if (query.responseOverwrite()) { + responseToProcess = choices.at(0); + } else if (query.responseSkip()) { + responseToProcess = choices.at(1); + } else if (query.responseOverwriteAll()) { + responseToProcess = choices.at(2); + } else if (query.responseAutoSkip()) { + responseToProcess = choices.at(3); + } else if (query.responseCancelled()) { + if (choices.count() < 5) { // If the program has no way to cancel the extraction, we resort to killing it + return doKill(); + } + responseToProcess = choices.at(4); + } + + Q_ASSERT(!responseToProcess.isEmpty()); + + responseToProcess += QLatin1Char( '\n' ); + + writeToProcess(responseToProcess.toLocal8Bit()); + + return true; +} + +bool CliInterface::checkForErrorMessage(const QString& line, int parameterIndex) +{ + QList patterns; + + if (m_patternCache.contains(parameterIndex)) { + patterns = m_patternCache.value(parameterIndex); + } else { + if (!m_param.contains(parameterIndex)) { + return false; + } + + foreach(const QString& rawPattern, m_param.value(parameterIndex).toStringList()) { + patterns << QRegExp(rawPattern); + } + m_patternCache[parameterIndex] = patterns; + } + + foreach(const QRegExp& pattern, patterns) { + if (pattern.indexIn(line) != -1) { + return true; + } + } + return false; +} + +bool CliInterface::doKill() +{ + if (m_process) { + // Give some time for the application to finish gracefully + m_abortingOperation = true; + if (!m_process->waitForFinished(5)) { + m_process->kill(); + } + m_abortingOperation = false; + + return true; + } + + return false; +} + +bool CliInterface::doSuspend() +{ + return false; +} + +bool CliInterface::doResume() +{ + return false; +} + +void CliInterface::substituteListVariables(QStringList& params) +{ + for (int i = 0; i < params.size(); ++i) { + const QString parameter = params.at(i); + + if (parameter == QLatin1String( "$Archive" )) { + params[i] = filename(); + } + } +} + +QString CliInterface::escapeFileName(const QString& fileName) const +{ + return fileName; +} + +void CliInterface::writeToProcess(const QByteArray& data) +{ + Q_ASSERT(m_process); + Q_ASSERT(!data.isNull()); + + kDebug() << "Writing" << data << "to the process"; + +#ifdef Q_OS_WIN + m_process->write(data); +#else + m_process->pty()->write(data); +#endif +} + +} + +#include "cliinterface.moc" diff --git a/ark/kerfuffle/cliinterface.h b/ark/kerfuffle/cliinterface.h new file mode 100644 index 00000000..b4d79ed2 --- /dev/null +++ b/ark/kerfuffle/cliinterface.h @@ -0,0 +1,349 @@ +/* + * ark -- archiver for the KDE project + * + * Copyright (C) 2009 Harald Hvaal + * Copyright (C) 2009-2011 Raphael Kubo da Costa + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ( INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION ) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef CLIINTERFACE_H +#define CLIINTERFACE_H + +#include "archiveinterface.h" +#include "kerfuffle_export.h" +#include +#include + +class KProcess; +class KPtyProcess; + +namespace Kerfuffle +{ + +enum CliInterfaceParameters { + + ///////////////[ COMMON ]///////////// + + /** + * Bool (default false) + * Will look for the %-sign in the stdout while working, in the form of + * (2%, 14%, 35%, etc etc), and report progress based upon this + */ + CaptureProgress = 0, + + /** + * QString + * Default: empty + * A regexp pattern that matches the program's password prompt. + */ + PasswordPromptPattern, + + ///////////////[ LIST ]///////////// + + /** + * QStringList + * The names to the program that will handle listing of this + * archive (eg "rar"). Will be searched for in PATH + */ + ListProgram, + /** + * QStringList + * The arguments that are passed to the program above for + * listing the archive. Special strings that will be + * substituted: + * $Archive - the path of the archive + */ + ListArgs, + + ///////////////[ EXTRACT ]///////////// + + /** + * QStringList + * The names to the program that will handle extracting of this + * archive (eg "rar"). Will be searched for in PATH + */ + ExtractProgram, + /** + * QStringList + * The arguments that are passed to the program above for + * extracting the archive. Special strings that will be + * substituted: + * $Archive - the path of the archive + * $Files - the files selected to be extracted, if any + * $PreservePathSwitch - the flag for extracting with full paths + * $RootNodeSwitch - the internal work dir in the archive (for example + * when the user has dragged a folder from the archive and wants it + * extracted relative to it) + * $PasswordSwitch - the switch setting the password. Note that this + * will not be inserted unless the listing function has emitted an + * entry with the IsPasswordProtected property set to true. + */ + ExtractArgs, + /** + * Bool (default false) + * When passing directories to the extract program, do not + * include trailing slashes + * e.g. if the user selected "foo/" and "foo/bar" in the gui, the + * paths "foo" and "foo/bar" will be sent to the program. + */ + NoTrailingSlashes, + /** + * QStringList + * This should be a qstringlist with either two elements. The first + * string is what PreservePathSwitch in the ExtractArgs will be replaced + * with if PreservePath is True/enabled. The second is for the disabled + * case. An empty string means that the argument will not be used in + * that case. + * Example: for rar, "x" means extract with full paths, and "e" means + * extract without full paths. in this case we will use the stringlist + * ("x", "e"). Or, for another format that might use the switch + * "--extractFull" for preservePaths, and nothing otherwise: we use the + * stringlist ("--extractFull", "") + */ + PreservePathSwitch, + /** + * QStringList (default empty) + * The format of the root node switch. The variable $Path will be + * substituted for the path string. + * Example: ("--internalPath=$Path) + * or ("--path", "$Path") + */ + RootNodeSwitch, + /** + * QStringList (default empty) + * The format of the root node switch. The variable $Password will be + * substituted for the password string. NOTE: supplying passwords + * through a virtual terminal is not supported (yet?), because this + * is not cross platform compatible. As of KDE 4.3 there are no plans to + * change this. + * Example: ("-p$Password) + * or ("--password", "$Password") + */ + PasswordSwitch, + /** + * QString + * This is a regexp, defining how to recognize a "File already exists" + * prompt when extracting. It should have one captured string, which is + * the filename of the file/folder that already exists. + */ + FileExistsExpression, + /** + * int + * This sets on what output channel the FileExistsExpression regex + * should be applied on, in other words, on what stream the "file + * exists" output will appear in. Values accepted: + * 0 - Standard error, stderr (default) + * 1 - Standard output, stdout + */ + FileExistsMode, + /** + * QStringList + * The various responses that can be supplied as a response to the + * "file exists" prompt. The various items are to be supplied in the + * following order: + * index 0 - Yes (overwrite) + * index 1 - No (skip/do not overwrite) + * index 2 - All (overwrite all) + * index 3 - Do not overwrite any files (autoskip) + * index 4 - Cancel operation + */ + FileExistsInput, + + ///////////////[ DELETE ]///////////// + + /** + * QStringList + * The names to the program that will handle deleting of elements in this + * archive format (eg "rar"). Will be searched for in PATH + */ + DeleteProgram, + /** + * QStringList + * The arguments that are passed to the program above for + * deleting from the archive. Special strings that will be + * substituted: + * $Archive - the path of the archive + * $Files - the files selected to be deleted + */ + DeleteArgs, + /** + * QStringList + * Default: empty + * A list of regexp patterns that will cause the extraction to exit + * with a general fail message + */ + ExtractionFailedPatterns, + /** + * QStringList + * Default: empty + * A list of regexp patterns that will alert the user that the password + * was wrong. + */ + WrongPasswordPatterns, + + ///////////////[ ADD ]///////////// + + /** + * QStringList + * The names to the program that will handle adding in this + * archive format (eg "rar"). Will be searched for in PATH + */ + AddProgram, + /** + * QStringList + * The arguments that are passed to the program above for + * adding to the archive. Special strings that will be + * substituted: + * $Archive - the path of the archive + * $Files - the files selected to be added + */ + AddArgs +}; + +typedef QHash ParameterList; + +class KERFUFFLE_EXPORT CliInterface : public ReadWriteArchiveInterface +{ + Q_OBJECT + +public: + enum OperationMode { + List, Copy, Add, Delete + }; + OperationMode m_operationMode; + + explicit CliInterface(QObject *parent, const QVariantList & args); + virtual ~CliInterface(); + + virtual bool list(); + virtual bool copyFiles(const QList & files, const QString & destinationDirectory, ExtractionOptions options); + virtual bool addFiles(const QStringList & files, const CompressionOptions& options); + virtual bool deleteFiles(const QList & files); + + virtual ParameterList parameterList() const = 0; + virtual bool readListLine(const QString &line) = 0; + + bool doKill(); + bool doSuspend(); + bool doResume(); + + /** + * Returns the list of characters which are preceded by a + * backslash when a file name in an archive is passed to + * a program. + * + * @see setEscapedCharacters(). + */ + QString escapedCharacters(); + + /** + * Sets which characters will be preceded by a backslash when + * a file name in an archive is passed to a program. + * + * @see escapedCharacters(). + */ + void setEscapedCharacters(const QString& characters); + + /** + * Sets if the listing should include empty lines. + * + * The default value is false. + */ + void setListEmptyLines(bool emptyLines); + +private: + void substituteListVariables(QStringList& params); + + void cacheParameterList(); + + /** + * Checks whether a line of the program's output is a password prompt. + * + * It uses the regular expression in the @c PasswordPromptPattern parameter + * for the check. + * + * @param line A line of the program's output. + * + * @return @c true if the given @p line is a password prompt, @c false + * otherwise. + */ + + bool checkForPasswordPromptMessage(const QString& line); + + bool checkForFileExistsMessage(const QString& line); + bool handleFileExistsMessage(const QString& filename); + bool checkForErrorMessage(const QString& line, int parameterIndex); + void handleLine(const QString& line); + + void failOperation(); + + /** + * Run @p programName with the given @p arguments. + * The method waits until @p programName is finished to exit. + * + * @param programName The program that will be run (not the whole path). + * @param arguments A list of arguments that will be passed to the program. + * + * @return @c true if the program was found and the process ran correctly, + * @c false otherwise. + */ + bool runProcess(const QStringList& programNames, const QStringList& arguments); + + /** + * Performs any additional escaping and processing on @p fileName + * before passing it to the underlying process. + * + * The default implementation returns @p fileName unchanged. + * + * @param fileName String to escape. + */ + virtual QString escapeFileName(const QString &fileName) const; + + /** + * Wrapper around KProcess::write() or KPtyDevice::write(), depending on + * the platform. + */ + void writeToProcess(const QByteArray& data); + + QByteArray m_stdOutData; + QRegExp m_existsPattern; + QRegExp m_passwordPromptPattern; + QHash > m_patternCache; + +#ifdef Q_OS_WIN + KProcess *m_process; +#else + KPtyProcess *m_process; +#endif + + ParameterList m_param; + QVariantList m_removedFiles; + bool m_listEmptyLines; + bool m_abortingOperation; + +private slots: + void readStdout(bool handleAll = false); + void processFinished(int exitCode, QProcess::ExitStatus exitStatus); +}; +} + +#endif /* CLIINTERFACE_H */ diff --git a/ark/kerfuffle/extractiondialog.cpp b/ark/kerfuffle/extractiondialog.cpp new file mode 100644 index 00000000..f665bc8c --- /dev/null +++ b/ark/kerfuffle/extractiondialog.cpp @@ -0,0 +1,234 @@ +/* + * ark -- archiver for the KDE project + * + * Copyright (C) 2009 Harald Hvaal + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ( INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION ) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "extractiondialog.h" +#include "settings.h" + +#include +#include +#include +#include +#include +#include + +#include + +#include "ui_extractiondialog.h" + +namespace Kerfuffle +{ + +class ExtractionDialogUI: public QFrame, public Ui::ExtractionDialog +{ +public: + ExtractionDialogUI(QWidget *parent = 0) + : QFrame(parent) { + setupUi(this); + } +}; + +ExtractionDialog::ExtractionDialog(QWidget *parent) + : KDirSelectDialog(KUrl(), false, parent) +{ + m_ui = new ExtractionDialogUI(this); + + mainWidget()->layout()->addWidget(m_ui); + setCaption(i18nc("@title:window", "Extract")); + m_ui->iconLabel->setPixmap(DesktopIcon(QLatin1String( "archive-extract" ))); + + m_ui->filesToExtractGroupBox->hide(); + m_ui->allFilesButton->setChecked(true); + m_ui->extractAllLabel->show(); + + setSingleFolderArchive(false); + + m_ui->autoSubfolders->hide(); + + loadSettings(); + + connect(this, SIGNAL(finished(int)), SLOT(writeSettings())); +} + +void ExtractionDialog::loadSettings() +{ + setOpenDestinationFolderAfterExtraction(ArkSettings::openDestinationFolderAfterExtraction()); + setCloseAfterExtraction(ArkSettings::closeAfterExtraction()); + setPreservePaths(ArkSettings::preservePaths()); +} + +void ExtractionDialog::setSingleFolderArchive(bool value) +{ + m_ui->singleFolderGroup->setChecked(!value); +} + +void ExtractionDialog::batchModeOption() +{ + m_ui->autoSubfolders->show(); + m_ui->autoSubfolders->setEnabled(true); + m_ui->singleFolderGroup->hide(); + m_ui->extractAllLabel->setText(i18n("Extract multiple archives")); +} + +void ExtractionDialog::accept() +{ + if (extractToSubfolder()) { + if (subfolder().contains(QLatin1String( "/" ))) { + KMessageBox::error(NULL, i18n("The subfolder name may not contain the character '/'.")); + return; + } + + const QString pathWithSubfolder = url().pathOrUrl(KUrl::AddTrailingSlash) + subfolder(); + + while (1) { + if (KIO::NetAccess::exists(pathWithSubfolder, KIO::NetAccess::SourceSide, 0)) { + if (QFileInfo(pathWithSubfolder).isDir()) { + int overwrite = KMessageBox::questionYesNoCancel(0, i18nc("@info", "The folder %1 already exists. Are you sure you want to extract here?", pathWithSubfolder), i18n("Folder exists"), KGuiItem(i18n("Extract here")), KGuiItem(i18n("Retry")), KGuiItem(i18n("Cancel"))); + + if (overwrite == KMessageBox::No) { + // The user clicked Retry. + continue; + } else if (overwrite == KMessageBox::Cancel) { + return; + } + } else { + KMessageBox::detailedError(0, + i18nc("@info", "The folder %1 could not be created.", subfolder()), + i18nc("@info", "%1 already exists, but is not a folder.", subfolder())); + return; + } + } else if (!KIO::NetAccess::mkdir(pathWithSubfolder, 0)) { + KMessageBox::detailedError(0, + i18nc("@info", "The folder %1 could not be created.", subfolder()), + i18n("Please check your permissions to create it.")); + return; + } + break; + } + } + + KDirSelectDialog::accept(); +} + +void ExtractionDialog::setSubfolder(const QString& subfolder) +{ + m_ui->subfolder->setText(subfolder); +} + +QString ExtractionDialog::subfolder() const +{ + return m_ui->subfolder->text(); +} + +ExtractionDialog::~ExtractionDialog() +{ + delete m_ui; + m_ui = 0; +} + +void ExtractionDialog::setShowSelectedFiles(bool value) +{ + if (value) { + m_ui->filesToExtractGroupBox->show(); + m_ui->selectedFilesButton->setChecked(true); + m_ui->extractAllLabel->hide(); + } else { + m_ui->filesToExtractGroupBox->hide(); + m_ui->selectedFilesButton->setChecked(false); + m_ui->extractAllLabel->show(); + } +} + +bool ExtractionDialog::extractAllFiles() const +{ + return m_ui->allFilesButton->isChecked(); +} + +void ExtractionDialog::setAutoSubfolder(bool value) +{ + m_ui->autoSubfolders->setChecked(value); +} + +bool ExtractionDialog::autoSubfolders() const +{ + return m_ui->autoSubfolders->isChecked(); +} + +bool ExtractionDialog::extractToSubfolder() const +{ + return m_ui->singleFolderGroup->isChecked(); +} + +void ExtractionDialog::setOpenDestinationFolderAfterExtraction(bool value) +{ + m_ui->openFolderCheckBox->setChecked(value); +} + +void ExtractionDialog::setCloseAfterExtraction(bool value) +{ + m_ui->closeAfterExtraction->setChecked(value); +} + +void ExtractionDialog::setPreservePaths(bool value) +{ + m_ui->preservePaths->setChecked(value); +} + +bool ExtractionDialog::preservePaths() const +{ + return m_ui->preservePaths->isChecked(); +} + +bool ExtractionDialog::openDestinationAfterExtraction() const +{ + return m_ui->openFolderCheckBox->isChecked(); +} + +bool ExtractionDialog::closeAfterExtraction() const +{ + return m_ui->closeAfterExtraction->isChecked(); +} + +KUrl ExtractionDialog::destinationDirectory() const +{ + if (extractToSubfolder()) { + return QString(url().pathOrUrl(KUrl::AddTrailingSlash) + subfolder() + QLatin1Char( '/' )); + } else { + return url().pathOrUrl(KUrl::AddTrailingSlash); + } +} + +void ExtractionDialog::writeSettings() +{ + ArkSettings::setOpenDestinationFolderAfterExtraction(openDestinationAfterExtraction()); + ArkSettings::setCloseAfterExtraction(closeAfterExtraction()); + ArkSettings::setPreservePaths(preservePaths()); + ArkSettings::self()->writeConfig(); +} + +} + +#include "extractiondialog.moc" diff --git a/ark/kerfuffle/extractiondialog.h b/ark/kerfuffle/extractiondialog.h new file mode 100644 index 00000000..03e773b8 --- /dev/null +++ b/ark/kerfuffle/extractiondialog.h @@ -0,0 +1,78 @@ +/* + * ark -- archiver for the KDE project + * + * Copyright (C) 2007 Henrique Pinto + * Copyright (C) 2008 Harald Hvaal + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ( INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION ) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef EXTRACTIONDIALOG_H +#define EXTRACTIONDIALOG_H + +#include "kerfuffle_export.h" + +#include + +#include + +namespace Kerfuffle +{ +class KERFUFFLE_EXPORT ExtractionDialog : public KDirSelectDialog +{ + Q_OBJECT +public: + ExtractionDialog(QWidget *parent = 0); + virtual ~ExtractionDialog(); + + void setShowSelectedFiles(bool); + void setSingleFolderArchive(bool); + void setPreservePaths(bool); + void batchModeOption(); + void setOpenDestinationFolderAfterExtraction(bool); + void setCloseAfterExtraction(bool); + void setAutoSubfolder(bool value); + + bool extractAllFiles() const; + bool openDestinationAfterExtraction() const; + bool closeAfterExtraction() const; + bool extractToSubfolder() const; + bool autoSubfolders() const; + bool preservePaths() const; + KUrl destinationDirectory() const; + QString subfolder() const; + virtual void accept(); + +public Q_SLOTS: + void setSubfolder(const QString& subfolder); + +private Q_SLOTS: + void writeSettings(); + +private: + void loadSettings(); + + class ExtractionDialogUI *m_ui; +}; +} + +#endif // EXTRACTIONDIALOG_H diff --git a/ark/kerfuffle/extractiondialog.ui b/ark/kerfuffle/extractiondialog.ui new file mode 100644 index 00000000..9a9cc317 --- /dev/null +++ b/ark/kerfuffle/extractiondialog.ui @@ -0,0 +1,220 @@ + + Henrique Pinto <henrique.pinto@kdemail.net> + ExtractionDialog + + + + 0 + 0 + 314 + 422 + + + + + 0 + 0 + + + + Extraction Dialog + + + + + + + + + 0 + 0 + + + + + 256 + 256 + + + + + + + ../../../../../kde-devel/ark-trunk/pics/ox32-action-ark_extract.png + + + false + + + Qt::AlignCenter + + + + + + + + 0 + 0 + + + + + 15 + 75 + true + + + + Extract All Files + + + + + + + + + &Extraction into subfolder: + + + true + + + false + + + + + + + 0 + 0 + + + + true + + + + + + + + + + + 0 + 0 + + + + Options + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Open &destination folder after extraction + + + + + + + Close &Ark after extraction + + + + + + + &Preserve paths when extracting + + + + + + + &Automatically create subfolders + + + + + + + + + + + 0 + 0 + + + + Extract + + + false + + + false + + + + + + + 0 + 0 + + + + &Selected files only + + + true + + + + + + + + 0 + 0 + + + + All &files + + + + + + + + + + + KLineEdit + QLineEdit +

klineedit.h
+ + + + + diff --git a/ark/kerfuffle/jobs.cpp b/ark/kerfuffle/jobs.cpp new file mode 100644 index 00000000..472a6e05 --- /dev/null +++ b/ark/kerfuffle/jobs.cpp @@ -0,0 +1,346 @@ +/* + * Copyright (c) 2007 Henrique Pinto + * Copyright (c) 2008-2009 Harald Hvaal + * Copyright (c) 2009-2012 Raphael Kubo da Costa + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ( INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION ) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "jobs.h" + +#include + +#include +#include + +//#define DEBUG_RACECONDITION + +namespace Kerfuffle +{ + +class Job::Private : public QThread +{ +public: + Private(Job *job, QObject *parent = 0) + : QThread(parent) + , q(job) + { + connect(q, SIGNAL(result(KJob*)), SLOT(quit())); + } + + virtual void run(); + +private: + Job *q; +}; + +void Job::Private::run() +{ + q->doWork(); + + if (q->isRunning()) { + exec(); + } + +#ifdef DEBUG_RACECONDITION + QThread::sleep(2); +#endif +} + +Job::Job(ReadOnlyArchiveInterface *interface, QObject *parent) + : KJob(parent) + , m_archiveInterface(interface) + , m_isRunning(false) + , d(new Private(this)) +{ + static bool onlyOnce = false; + if (!onlyOnce) { + qRegisterMetaType >("QPair"); + onlyOnce = true; + } + + setCapabilities(KJob::Killable); +} + +Job::~Job() +{ + if (d->isRunning()) { + d->wait(); + } + + delete d; +} + +ReadOnlyArchiveInterface *Job::archiveInterface() +{ + return m_archiveInterface; +} + +bool Job::isRunning() const +{ + return m_isRunning; +} + +void Job::start() +{ + m_isRunning = true; + d->start(); +} + +void Job::emitResult() +{ + m_isRunning = false; + KJob::emitResult(); +} + +void Job::connectToArchiveInterfaceSignals() +{ + connect(archiveInterface(), SIGNAL(error(QString,QString)), SLOT(onError(QString,QString))); + connect(archiveInterface(), SIGNAL(entry(ArchiveEntry)), SLOT(onEntry(ArchiveEntry))); + connect(archiveInterface(), SIGNAL(entryRemoved(QString)), SLOT(onEntryRemoved(QString))); + connect(archiveInterface(), SIGNAL(progress(double)), SLOT(onProgress(double))); + connect(archiveInterface(), SIGNAL(info(QString)), SLOT(onInfo(QString))); + connect(archiveInterface(), SIGNAL(finished(bool)), SLOT(onFinished(bool)), Qt::DirectConnection); + connect(archiveInterface(), SIGNAL(userQuery(Query*)), SLOT(onUserQuery(Query*))); +} + +void Job::onError(const QString & message, const QString & details) +{ + Q_UNUSED(details) + + setError(1); + setErrorText(message); +} + +void Job::onEntry(const ArchiveEntry & archiveEntry) +{ + emit newEntry(archiveEntry); +} + +void Job::onProgress(double value) +{ + setPercent(static_cast(100.0*value)); +} + +void Job::onInfo(const QString& info) +{ + emit infoMessage(this, info); +} + +void Job::onEntryRemoved(const QString & path) +{ + emit entryRemoved(path); +} + +void Job::onFinished(bool result) +{ + kDebug() << result; + + archiveInterface()->disconnect(this); + + emitResult(); +} + +void Job::onUserQuery(Query *query) +{ + emit userQuery(query); +} + +bool Job::doKill() +{ + kDebug(); + bool ret = archiveInterface()->doKill(); + if (!ret) { + kDebug() << "Killing does not seem to be supported here."; + } + return ret; +} + +ListJob::ListJob(ReadOnlyArchiveInterface *interface, QObject *parent) + : Job(interface, parent) + , m_isSingleFolderArchive(true) + , m_isPasswordProtected(false) + , m_extractedFilesSize(0) +{ + connect(this, SIGNAL(newEntry(ArchiveEntry)), + this, SLOT(onNewEntry(ArchiveEntry))); +} + +void ListJob::doWork() +{ + emit description(this, i18n("Loading archive...")); + connectToArchiveInterfaceSignals(); + bool ret = archiveInterface()->list(); + + if (!archiveInterface()->waitForFinishedSignal()) { + onFinished(ret); + } +} + +qlonglong ListJob::extractedFilesSize() const +{ + return m_extractedFilesSize; +} + +bool ListJob::isPasswordProtected() const +{ + return m_isPasswordProtected; +} + +bool ListJob::isSingleFolderArchive() const +{ + return m_isSingleFolderArchive; +} + +void ListJob::onNewEntry(const ArchiveEntry& entry) +{ + m_extractedFilesSize += entry[ Size ].toLongLong(); + m_isPasswordProtected |= entry [ IsPasswordProtected ].toBool(); + + if (m_isSingleFolderArchive) { + const QString fileName(entry[FileName].toString()); + const QString basePath(fileName.split(QLatin1Char( '/' )).at(0)); + + if (m_basePath.isEmpty()) { + m_basePath = basePath; + m_subfolderName = basePath; + } else { + if (m_basePath != basePath) { + m_isSingleFolderArchive = false; + m_subfolderName.clear(); + } + } + } +} + +QString ListJob::subfolderName() const +{ + return m_subfolderName; +} + +ExtractJob::ExtractJob(const QVariantList& files, const QString& destinationDir, ExtractionOptions options, ReadOnlyArchiveInterface *interface, QObject *parent) + : Job(interface, parent) + , m_files(files) + , m_destinationDir(destinationDir) + , m_options(options) +{ + setDefaultOptions(); +} + +void ExtractJob::doWork() +{ + QString desc; + if (m_files.count() == 0) { + desc = i18n("Extracting all files"); + } else { + desc = i18np("Extracting one file", "Extracting %1 files", m_files.count()); + } + emit description(this, desc); + + connectToArchiveInterfaceSignals(); + + kDebug() << "Starting extraction with selected files:" + << m_files + << "Destination dir:" << m_destinationDir + << "Options:" << m_options; + + bool ret = archiveInterface()->copyFiles(m_files, m_destinationDir, m_options); + + if (!archiveInterface()->waitForFinishedSignal()) { + onFinished(ret); + } +} + +void ExtractJob::setDefaultOptions() +{ + ExtractionOptions defaultOptions; + + defaultOptions[QLatin1String("PreservePaths")] = false; + + ExtractionOptions::const_iterator it = defaultOptions.constBegin(); + for (; it != defaultOptions.constEnd(); ++it) { + if (!m_options.contains(it.key())) { + m_options[it.key()] = it.value(); + } + } +} + +QString ExtractJob::destinationDirectory() const +{ + return m_destinationDir; +} + +ExtractionOptions ExtractJob::extractionOptions() const +{ + return m_options; +} + +AddJob::AddJob(const QStringList& files, const CompressionOptions& options , ReadWriteArchiveInterface *interface, QObject *parent) + : Job(interface, parent) + , m_files(files) + , m_options(options) +{ +} + +void AddJob::doWork() +{ + emit description(this, i18np("Adding a file", "Adding %1 files", m_files.count())); + + ReadWriteArchiveInterface *m_writeInterface = + qobject_cast(archiveInterface()); + + Q_ASSERT(m_writeInterface); + + connectToArchiveInterfaceSignals(); + bool ret = m_writeInterface->addFiles(m_files, m_options); + + if (!archiveInterface()->waitForFinishedSignal()) { + onFinished(ret); + } +} + +DeleteJob::DeleteJob(const QVariantList& files, ReadWriteArchiveInterface *interface, QObject *parent) + : Job(interface, parent) + , m_files(files) +{ +} + +void DeleteJob::doWork() +{ + emit description(this, i18np("Deleting a file from the archive", "Deleting %1 files", m_files.count())); + + ReadWriteArchiveInterface *m_writeInterface = + qobject_cast(archiveInterface()); + + Q_ASSERT(m_writeInterface); + + connectToArchiveInterfaceSignals(); + int ret = m_writeInterface->deleteFiles(m_files); + + if (!archiveInterface()->waitForFinishedSignal()) { + onFinished(ret); + } +} + +} // namespace Kerfuffle + +#include "jobs.moc" diff --git a/ark/kerfuffle/jobs.h b/ark/kerfuffle/jobs.h new file mode 100644 index 00000000..d704dfc7 --- /dev/null +++ b/ark/kerfuffle/jobs.h @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2007 Henrique Pinto + * Copyright (c) 2008-2009 Harald Hvaal + * Copyright (c) 2009-2012 Raphael Kubo da Costa + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ( INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION ) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef JOBS_H +#define JOBS_H + +#include "kerfuffle_export.h" +#include "archiveinterface.h" +#include "archive.h" +#include "queries.h" + +#include +#include +#include +#include + +namespace Kerfuffle +{ + +class ThreadExecution; + +class KERFUFFLE_EXPORT Job : public KJob +{ + Q_OBJECT + +public: + void start(); + + bool isRunning() const; + +protected: + Job(ReadOnlyArchiveInterface *interface, QObject *parent = 0); + virtual ~Job(); + virtual bool doKill(); + virtual void emitResult(); + + ReadOnlyArchiveInterface *archiveInterface(); + + void connectToArchiveInterfaceSignals(); + +public slots: + virtual void doWork() = 0; + +protected slots: + virtual void onError(const QString &message, const QString &details); + virtual void onInfo(const QString &info); + virtual void onEntry(const ArchiveEntry &archiveEntry); + virtual void onProgress(double progress); + virtual void onEntryRemoved(const QString &path); + virtual void onFinished(bool result); + virtual void onUserQuery(Query *query); + +signals: + void entryRemoved(const QString & entry); + void error(const QString& errorMessage, const QString& details); + void newEntry(const ArchiveEntry &); + void userQuery(Kerfuffle::Query*); + +private: + ReadOnlyArchiveInterface *m_archiveInterface; + + bool m_isRunning; + + class Private; + Private * const d; +}; + +class KERFUFFLE_EXPORT ListJob : public Job +{ + Q_OBJECT + +public: + explicit ListJob(ReadOnlyArchiveInterface *interface, QObject *parent = 0); + + qlonglong extractedFilesSize() const; + bool isPasswordProtected() const; + bool isSingleFolderArchive() const; + QString subfolderName() const; + +public slots: + virtual void doWork(); + +private: + bool m_isSingleFolderArchive; + bool m_isPasswordProtected; + QString m_subfolderName; + QString m_basePath; + qlonglong m_extractedFilesSize; + +private slots: + void onNewEntry(const ArchiveEntry&); +}; + +class KERFUFFLE_EXPORT ExtractJob : public Job +{ + Q_OBJECT + +public: + ExtractJob(const QVariantList& files, const QString& destinationDir, ExtractionOptions options, ReadOnlyArchiveInterface *interface, QObject *parent = 0); + + QString destinationDirectory() const; + ExtractionOptions extractionOptions() const; + +public slots: + virtual void doWork(); + +private: + // TODO: Maybe this should be a method if ExtractionOptions were a class? + void setDefaultOptions(); + + QVariantList m_files; + QString m_destinationDir; + ExtractionOptions m_options; +}; + +class KERFUFFLE_EXPORT AddJob : public Job +{ + Q_OBJECT + +public: + AddJob(const QStringList& files, const CompressionOptions& options, ReadWriteArchiveInterface *interface, QObject *parent = 0); + +public slots: + virtual void doWork(); + +private: + QStringList m_files; + CompressionOptions m_options; +}; + +class KERFUFFLE_EXPORT DeleteJob : public Job +{ + Q_OBJECT + +public: + DeleteJob(const QVariantList& files, ReadWriteArchiveInterface *interface, QObject *parent = 0); + +public slots: + virtual void doWork(); + +private: + QVariantList m_files; +}; + +} // namespace Kerfuffle + +#endif // JOBS_H diff --git a/ark/kerfuffle/kerfufflePlugin.desktop b/ark/kerfuffle/kerfufflePlugin.desktop new file mode 100644 index 00000000..3999d24f --- /dev/null +++ b/ark/kerfuffle/kerfufflePlugin.desktop @@ -0,0 +1,74 @@ +[Desktop Entry] +Type=ServiceType +X-KDE-ServiceType=Kerfuffle/Plugin +Comment=Plugin for handling of archive formats for the Kerfuffle library +Comment[ar]=ملحق للتحكم بهيئات الأرشيف لمكتبة Kerfuffle +Comment[ast]=Aplicación pa remanar los formatos de ficheru de la llibrería Kerfuffle +Comment[bg]=Приствка за работа с архиви за Kerfuffle +Comment[bs]=Priključak za rukovanje formatima arhiva za biblioteku Kerfafl +Comment[ca]=Connector per gestionar els formats d'arxiu per la biblioteca Kerfuffle +Comment[ca@valencia]=Connector per gestionar els formats d'arxiu per la biblioteca Kerfuffle +Comment[cs]=Modul pro správu formátů archivů pro knihovnu Kerfuffle +Comment[da]=Plugin til håndtering af arkivformater til Kerfuffle-biblioteket +Comment[de]=Modul zum Umgang mit Archivformaten der Kerfuffle-Bibliothek +Comment[el]=Πρόσθετο χειρισμού μορφών αρχειοθήκης για τη βιβλιοθήκη Kerfuffle +Comment[en_GB]=Plugin for handling of archive formats for the Kerfuffle library +Comment[es]=Complemento para manejar formatos de archivo de la biblioteca Kerfuffle +Comment[et]=Kerfuffle'i teegi arhiivifailide haldamise plugin +Comment[eu]=Kerfuffle liburutegiaren artxibo-formatuak erabiltzeko plugina +Comment[fi]=Kerfuffle-kirjaston pakettimuotojen tuki +Comment[fr]=Module externe de gestion des formats d'archives pour la bibliothèque « Kerfuffle » +Comment[ga]=Breiseán a láimhseálann formáidí cartlainne thar ceann na leabharlainne Kerfuffle +Comment[gl]=Extensión da biblioteca Kerfuffle para manexar formatos de arquivo +Comment[he]=תוסף לניהול פורמטי ארכיונים לספריית Kerfuffle +Comment[hne]=करफुफल लाइब्रेरी बर अभिलेख फारमेट हेंडल करे बर प्लगइन +Comment[hr]=Priključak za upravljanje arhivnim formatima za biblioteku Kerfuffle +Comment[hu]=Archívumkezelő modul a Kerfuffle programkönyvtárhoz +Comment[ia]=Plugin per manear formatos de archivo per le libreria Kerfuffle +Comment[id]=Pengaya untuk menangani format arsip pustaka Kerfuffle +Comment[it]=Estensione per gestire formati di archivio per la libreria Kerfuffle +Comment[ja]=Kerfuffle ライブラリのアーカイブ形式を扱うプラグイン +Comment[kk]=Kerfuffle жиын файлға арналған архив пішіндерімен айналасу плагині +Comment[km]=កម្មវិធីជំនួយ​សម្រាប់​គ្រប់គ្រង​ទ្រង់ទ្រាយ​ប័ណ្ណសារ​សម្រាប់​បណ្ណាល័យ Kerfuffle +Comment[ko]=Kerfuffle 라이브러리를 위한 압축 파일 플러그인 +Comment[lt]=Kerfuffle bibliotekos archyvų formatų palaikymo priedas +Comment[lv]=Arhīva failu tipu izmantošanas spraudnis Kerfuffle bibliotēkai. +Comment[mr]=कर्फफल लायब्ररी करिताचे संग्रह प्रकार हाताळणारे प्लगइन +Comment[nb]=Programtillegg som håndterer arkivformater, for Kerfuffle-biblioteket +Comment[nds]=Archievformaten-Moduul för de Kerfuffle-Bibliotheek +Comment[nl]=Plug-in voor het afhandelen van archiefformaten voor de Kerfuffle-bibliotheek +Comment[nn]=Programtillegg som handsamar arkivformat for Kerfuffle-biblioteket +Comment[pl]=Wtyczka do obsługi formatów archiwów w bibliotece Kerfuffle +Comment[pt]='Plugin' para lidar com os formatos de pacotes da biblioteca Kerfuffle +Comment[pt_BR]=Plugin para manipulação de formatos de arquivo, para a biblioteca Kerfuffle +Comment[ro]=Modul pentru pentru mînuirea formatelor de arhive pentru biblioteca Kerfuffle +Comment[ru]=Расширение для поддержки архивов с помощью библиотеки Kerfuffle +Comment[sk]=Modul pre spracovanie formátov archívov pre knižnicu Kerfuffle +Comment[sl]=Vstavek za ravnanje z arhivi za knjižnico Kerfuffle +Comment[sq]=Plugin që merret me formatet e arkivave për librarinë Kerfuffle +Comment[sr]=Прикључак за руковање форматима архива за библиотеку Керфафл +Comment[sr@ijekavian]=Прикључак за руковање форматима архива за библиотеку Керфафл +Comment[sr@ijekavianlatin]=Priključak za rukovanje formatima arhiva za biblioteku Kerfuffle +Comment[sr@latin]=Priključak za rukovanje formatima arhiva za biblioteku Kerfuffle +Comment[sv]=Insticksprogram för hantering av arkivformat för Kerfuffle-biblioteket +Comment[ta]=கெர்ஃபஃபில் ஃ நூலகத்துக்கு காப்பக வடிவமைப்பை கையாள சொருகிகள் +Comment[th]=ส่วนเสริมของไลบรารี Kerfuffle สำหรับจัดการแฟ้มจัดเก็บแบบต่าง ๆ +Comment[tr]=Kerfuffle kitaplığı için arşiv biçimleri eklentileri +Comment[uk]=Додаток для роботи з форматами архівів для бібліотеки Kerfuffle +Comment[x-test]=xxPlugin for handling of archive formats for the Kerfuffle libraryxx +Comment[zh_CN]=Kerfuffle 库的压缩归档格式处理插件 +Comment[zh_TW]=處理 Kerfuffle 函式庫壓縮格式的外掛程式 + +# Priority. The greater the better. +[PropertyDef::X-KDE-Priority] +Type=int + +# Revision of the plugin API. +[PropertyDef::X-KDE-Kerfuffle-APIRevision] +Type=int + +# Is the plugin read-write or not? (i.e., can it create archives?) +[PropertyDef::X-KDE-Kerfuffle-ReadWrite] +Type=bool + + diff --git a/ark/kerfuffle/kerfuffle_export.h b/ark/kerfuffle/kerfuffle_export.h new file mode 100644 index 00000000..f25c757a --- /dev/null +++ b/ark/kerfuffle/kerfuffle_export.h @@ -0,0 +1,53 @@ +/* + * This file is part of the KDE project + * + * Copyright (C) 2007 David Faure + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ( INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION ) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef KERFUFFLE_EXPORT_H +#define KERFUFFLE_EXPORT_H + +/* needed for KDE_EXPORT and KDE_IMPORT macros */ +#include +#include + +#ifndef KERFUFFLE_EXPORT +# if defined(MAKE_KERFUFFLE_LIB) +/* We are building this library */ +# define KERFUFFLE_EXPORT KDE_EXPORT +# else +/* We are using this library */ +# define KERFUFFLE_EXPORT KDE_IMPORT +# endif +#endif + +# ifndef KERFUFFLE_EXPORT_DEPRECATED +# define KERFUFFLE_EXPORT_DEPRECATED KDE_DEPRECATED KERFUFFLE_EXPORT +# endif + +#define KERFUFFLE_EXPORT_PLUGIN(p) \ + K_PLUGIN_FACTORY( ArkPluginFactory, registerPlugin< p >(); ) \ + K_EXPORT_PLUGIN( ArkPluginFactory("p") ) + +#endif diff --git a/ark/kerfuffle/queries.cpp b/ark/kerfuffle/queries.cpp new file mode 100644 index 00000000..2067d0f0 --- /dev/null +++ b/ark/kerfuffle/queries.cpp @@ -0,0 +1,204 @@ +/* + * ark -- archiver for the KDE project + * + * Copyright (C) 2008-2009 Harald Hvaal + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ( INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION ) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "queries.h" + +#include +#include +#include +#include + +#include +#include + +namespace Kerfuffle +{ +Query::Query() +{ + m_responseMutex.lock(); +} + +QVariant Query::response() +{ + return m_data.value(QLatin1String( "response" )); +} + +void Query::waitForResponse() +{ + kDebug(); + + //if there is no response set yet, wait + if (!m_data.contains(QLatin1String("response"))) { + m_responseCondition.wait(&m_responseMutex); + } + m_responseMutex.unlock(); +} + +void Query::setResponse(QVariant response) +{ + kDebug(); + + m_data[QLatin1String( "response" )] = response; + m_responseCondition.wakeAll(); +} + +OverwriteQuery::OverwriteQuery(const QString &filename) : + m_noRenameMode(false), + m_multiMode(true) +{ + m_data[QLatin1String( "filename" )] = filename; +} + +void OverwriteQuery::execute() +{ + // If we are being called from the KPart, the cursor is probably Qt::WaitCursor + // at the moment (#231974) + QApplication::setOverrideCursor(QCursor(Qt::ArrowCursor)); + + KIO::RenameDialog_Mode mode = (KIO::RenameDialog_Mode)(KIO::M_OVERWRITE | KIO::M_SKIP); + if (m_noRenameMode) { + mode = (KIO::RenameDialog_Mode)(mode | KIO::M_NORENAME); + } + if (m_multiMode) { + mode = (KIO::RenameDialog_Mode)(mode | KIO::M_MULTI); + } + + KUrl sourceUrl(m_data.value(QLatin1String( "filename" )).toString()); + KUrl destUrl(m_data.value(QLatin1String( "filename" )).toString()); + sourceUrl.cleanPath(); + destUrl.cleanPath(); + + QWeakPointer dialog = new KIO::RenameDialog( + NULL, + i18n("File already exists"), + sourceUrl, + destUrl, + mode); + dialog.data()->exec(); + + m_data[QLatin1String("newFilename")] = dialog.data()->newDestUrl().pathOrUrl(); + + setResponse(dialog.data()->result()); + + delete dialog.data(); + + QApplication::restoreOverrideCursor(); +} + +bool OverwriteQuery::responseCancelled() +{ + return m_data.value(QLatin1String( "response" )).toInt() == KIO::R_CANCEL; +} +bool OverwriteQuery::responseOverwriteAll() +{ + return m_data.value(QLatin1String( "response" )).toInt() == KIO::R_OVERWRITE_ALL; +} +bool OverwriteQuery::responseOverwrite() +{ + return m_data.value(QLatin1String( "response" )).toInt() == KIO::R_OVERWRITE; +} + +bool OverwriteQuery::responseRename() +{ + return m_data.value(QLatin1String( "response" )).toInt() == KIO::R_RENAME; +} + +bool OverwriteQuery::responseSkip() +{ + return m_data.value(QLatin1String( "response" )).toInt() == KIO::R_SKIP; +} + +bool OverwriteQuery::responseAutoSkip() +{ + return m_data.value(QLatin1String( "response" )).toInt() == KIO::R_AUTO_SKIP; +} + +QString OverwriteQuery::newFilename() +{ + return m_data.value(QLatin1String( "newFilename" )).toString(); +} + +void OverwriteQuery::setNoRenameMode(bool enableNoRenameMode) +{ + m_noRenameMode = enableNoRenameMode; +} + +bool OverwriteQuery::noRenameMode() +{ + return m_noRenameMode; +} + +void OverwriteQuery::setMultiMode(bool enableMultiMode) +{ + m_multiMode = enableMultiMode; +} + +bool OverwriteQuery::multiMode() +{ + return m_multiMode; +} + +PasswordNeededQuery::PasswordNeededQuery(const QString& archiveFilename, bool incorrectTryAgain) +{ + m_data[QLatin1String( "archiveFilename" )] = archiveFilename; + m_data[QLatin1String( "incorrectTryAgain" )] = incorrectTryAgain; +} + +void PasswordNeededQuery::execute() +{ + // If we are being called from the KPart, the cursor is probably Qt::WaitCursor + // at the moment (#231974) + QApplication::setOverrideCursor(QCursor(Qt::ArrowCursor)); + + QWeakPointer dlg = new KPasswordDialog; + dlg.data()->setPrompt(i18nc("@info", "The archive %1 is password protected. Please enter the password to extract the file.", m_data.value(QLatin1String( "archiveFilename" )).toString())); + + if (m_data.value(QLatin1String("incorrectTryAgain")).toBool()) { + dlg.data()->showErrorMessage(i18n("Incorrect password, please try again."), KPasswordDialog::PasswordError); + } + + const bool notCancelled = dlg.data()->exec(); + const QString password = dlg.data()->password(); + + m_data[QLatin1String("password")] = password; + setResponse(notCancelled && !password.isEmpty()); + + QApplication::restoreOverrideCursor(); + + delete dlg.data(); +} + +QString PasswordNeededQuery::password() +{ + return m_data.value(QLatin1String( "password" )).toString(); +} + +bool PasswordNeededQuery::responseCancelled() +{ + return !m_data.value(QLatin1String( "response" )).toBool(); +} +} diff --git a/ark/kerfuffle/queries.h b/ark/kerfuffle/queries.h new file mode 100644 index 00000000..0abf6890 --- /dev/null +++ b/ark/kerfuffle/queries.h @@ -0,0 +1,111 @@ +/* + * ark -- archiver for the KDE project + * + * Copyright (C) 2008 Harald Hvaal + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ( INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION ) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef QUERIES_H +#define QUERIES_H + +#include "kerfuffle_export.h" + +#include +#include +#include +#include +#include + +namespace Kerfuffle +{ + +typedef QHash QueryData; + +class KERFUFFLE_EXPORT Query +{ +public: + /** + * Execute the response. Will happen in the GUI thread, so it's + * safe to use widgets/gui elements here. Must call setResponse + * when done. + */ + virtual void execute() = 0; + + /** + * Will block until the response have been set + */ + void waitForResponse(); + + QVariant response(); + +protected: + /** + * Protected constructor + */ + Query(); + virtual ~Query() {} + + void setResponse(QVariant response); + + QueryData m_data; + +private: + QWaitCondition m_responseCondition; + QMutex m_responseMutex; +}; + +class KERFUFFLE_EXPORT OverwriteQuery : public Query +{ +public: + explicit OverwriteQuery(const QString& filename); + void execute(); + bool responseCancelled(); + bool responseOverwriteAll(); + bool responseOverwrite(); + bool responseRename(); + bool responseSkip(); + bool responseAutoSkip(); + QString newFilename(); + + void setNoRenameMode(bool enableNoRenameMode); + bool noRenameMode(); + void setMultiMode(bool enableMultiMode); + bool multiMode(); +private: + bool m_noRenameMode; + bool m_multiMode; +}; + +class KERFUFFLE_EXPORT PasswordNeededQuery : public Query +{ +public: + explicit PasswordNeededQuery(const QString& archiveFilename, bool incorrectTryAgain = false); + void execute(); + + bool responseCancelled(); + QString password(); +}; + +} + +#endif /* ifndef QUERIES_H */ diff --git a/ark/kerfuffle/settings.kcfgc b/ark/kerfuffle/settings.kcfgc new file mode 100644 index 00000000..de7ea883 --- /dev/null +++ b/ark/kerfuffle/settings.kcfgc @@ -0,0 +1,7 @@ +File=ark.kcfg +ClassName=ArkSettings +Singleton=true +Mutators=true +SetUserTexts=true +IncludeFiles=\"kerfuffle/kerfuffle_export.h\" +Visibility=KERFUFFLE_EXPORT diff --git a/ark/kerfuffle/tests/CMakeLists.txt b/ark/kerfuffle/tests/CMakeLists.txt new file mode 100644 index 00000000..c0285295 --- /dev/null +++ b/ark/kerfuffle/tests/CMakeLists.txt @@ -0,0 +1,21 @@ +include_directories(${KERFUFFLE_QJSON_INCLUDE_DIR}) + +set(RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + +set(JSONINTERFACE_SOURCES + jsonarchiveinterface.cpp + jsonparser.cpp +) +kde4_add_library(jsoninterface STATIC ${JSONINTERFACE_SOURCES}) + +macro(KERFUFFLE_UNIT_TESTS) + foreach(_testname ${ARGN}) + kde4_add_unit_test(${_testname} NOGUI ${_testname}.cpp) + target_link_libraries(${_testname} jsoninterface kerfuffle ${KDE4_KDEUI_LIBS} ${QT_QTTEST_LIBRARY} ${KERFUFFLE_QJSON_LIBRARIES}) + endforeach(_testname) +endmacro(KERFUFFLE_UNIT_TESTS) + +KERFUFFLE_UNIT_TESTS( + archivetest + jobstest +) diff --git a/ark/kerfuffle/tests/archivetest.cpp b/ark/kerfuffle/tests/archivetest.cpp new file mode 100644 index 00000000..ff268828 --- /dev/null +++ b/ark/kerfuffle/tests/archivetest.cpp @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2010-2011 Raphael Kubo da Costa + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ( INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION ) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "kerfuffle/archive.h" + +#include + +class ArchiveTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void testFileName(); + void testIsPasswordProtected(); + void testOpenNonExistentFile(); +}; + +QTEST_KDEMAIN_CORE(ArchiveTest) + +void ArchiveTest::testFileName() +{ + Kerfuffle::Archive *archive = Kerfuffle::Archive::create(QLatin1String("/tmp/foo.tar.gz"), this); + + if (!archive) { + QSKIP("There is no plugin to handle tar.gz files. Skipping test.", SkipSingle); + } + + QCOMPARE(archive->fileName(), QLatin1String("/tmp/foo.tar.gz")); +} + +void ArchiveTest::testIsPasswordProtected() +{ + Kerfuffle::Archive *archive; + + archive = Kerfuffle::Archive::create(QLatin1String(KDESRCDIR "data/archivetest_encrypted.zip"), this); + if (!archive) { + QSKIP("There is no plugin to handle zip files. Skipping test.", SkipSingle); + } + + QVERIFY(archive->isPasswordProtected()); + + archive->deleteLater(); + + archive = Kerfuffle::Archive::create(QLatin1String(KDESRCDIR "data/archivetest_unencrypted.zip"), this); + + QVERIFY(!archive->isPasswordProtected()); +} + +void ArchiveTest::testOpenNonExistentFile() +{ + QSKIP("How should we deal with files that do not exist? Should factory() return NULL?", SkipSingle); +} + +#include "archivetest.moc" diff --git a/ark/kerfuffle/tests/data/archive-deepsinglehierarchy.json b/ark/kerfuffle/tests/data/archive-deepsinglehierarchy.json new file mode 100644 index 00000000..d673c7c0 --- /dev/null +++ b/ark/kerfuffle/tests/data/archive-deepsinglehierarchy.json @@ -0,0 +1,20 @@ +[ + { + "FileName": "aDir/", + "IsDirectory": true + }, + { + "FileName": "aDir/b.txt" + }, + { + "FileName": "aDir/aDirInside/", + "IsDirectory": true + }, + { + "FileName": "aDir/aDirInside/anotherDir/", + "IsDirectory": true + }, + { + "FileName": "aDir/aDirInside/anotherDir/file.txt" + } +] diff --git a/ark/kerfuffle/tests/data/archive-multiplefolders.json b/ark/kerfuffle/tests/data/archive-multiplefolders.json new file mode 100644 index 00000000..92e9224c --- /dev/null +++ b/ark/kerfuffle/tests/data/archive-multiplefolders.json @@ -0,0 +1,16 @@ +[ + { + "FileName": "aDir/", + "IsDirectory": true + }, + { + "FileName": "aDir/b.txt" + }, + { + "FileName": "anotherDir/", + "IsDirectory": true + }, + { + "FileName": "anotherDir/file.txt" + } +] diff --git a/ark/kerfuffle/tests/data/archive-nodir-manyfiles.json b/ark/kerfuffle/tests/data/archive-nodir-manyfiles.json new file mode 100644 index 00000000..945c6168 --- /dev/null +++ b/ark/kerfuffle/tests/data/archive-nodir-manyfiles.json @@ -0,0 +1,8 @@ +[ + { + "FileName": "a.txt" + }, + { + "FileName": "file.txt" + } +] diff --git a/ark/kerfuffle/tests/data/archive-onetopfolder.json b/ark/kerfuffle/tests/data/archive-onetopfolder.json new file mode 100644 index 00000000..ec1e2ef5 --- /dev/null +++ b/ark/kerfuffle/tests/data/archive-onetopfolder.json @@ -0,0 +1,9 @@ +[ + { + "FileName": "aDir/", + "IsDirectory": true + }, + { + "FileName": "aDir/b.txt" + } +] diff --git a/ark/kerfuffle/tests/data/archive-password.json b/ark/kerfuffle/tests/data/archive-password.json new file mode 100644 index 00000000..a6abd9bf --- /dev/null +++ b/ark/kerfuffle/tests/data/archive-password.json @@ -0,0 +1,13 @@ +[ + { + "FileName": "foo.txt", + "IsPasswordProtected": true + }, + { + "FileName": "bar.txt" + }, + { + "FileName": "aDirectory/", + "IsDirectory": true + } +] diff --git a/ark/kerfuffle/tests/data/archive-singlefile.json b/ark/kerfuffle/tests/data/archive-singlefile.json new file mode 100644 index 00000000..21d49f77 --- /dev/null +++ b/ark/kerfuffle/tests/data/archive-singlefile.json @@ -0,0 +1,5 @@ +[ + { + "FileName": "a.txt" + } +] \ No newline at end of file diff --git a/ark/kerfuffle/tests/data/archive-unorderedsinglefolder.json b/ark/kerfuffle/tests/data/archive-unorderedsinglefolder.json new file mode 100644 index 00000000..28db1cff --- /dev/null +++ b/ark/kerfuffle/tests/data/archive-unorderedsinglefolder.json @@ -0,0 +1,16 @@ +[ + { + "FileName": "aDir/anotherDir/bar.txt" + }, + { + "FileName": "aDir/foo.txt" + }, + { + "FileName": "aDir/anotherDir/", + "IsDirectory": true + }, + { + "FileName": "aDir/", + "IsDirectory": true + } +] diff --git a/ark/kerfuffle/tests/data/archive001.json b/ark/kerfuffle/tests/data/archive001.json new file mode 100644 index 00000000..01a78601 --- /dev/null +++ b/ark/kerfuffle/tests/data/archive001.json @@ -0,0 +1,15 @@ +[ + { + "FileName": "a.txt" + }, + { + "FileName": "aDir/", + "IsDirectory": true + }, + { + "FileName": "aDir/b.txt" + }, + { + "FileName": "c.txt" + } +] diff --git a/ark/kerfuffle/tests/data/archive002.json b/ark/kerfuffle/tests/data/archive002.json new file mode 100644 index 00000000..5aa50b69 --- /dev/null +++ b/ark/kerfuffle/tests/data/archive002.json @@ -0,0 +1,18 @@ +[ + { + "FileName": "a.txt", + "Size": 5 + }, + { + "FileName": "aDir/", + "IsDirectory": true + }, + { + "FileName": "aDir/b.txt", + "Size": 954 + }, + { + "FileName": "c.txt", + "Size": 45000 + } +] diff --git a/ark/kerfuffle/tests/data/archivetest_encrypted.zip b/ark/kerfuffle/tests/data/archivetest_encrypted.zip new file mode 100644 index 0000000000000000000000000000000000000000..3a1093dfc47d64aeaeda4d72b09b5e63ef1acb2d GIT binary patch literal 196 zcmWIWW@h1H;ACK6$m{aAF53gR#T E07)Gj1poj5 literal 0 HcmV?d00001 diff --git a/ark/kerfuffle/tests/data/simplearchive.tar.gz b/ark/kerfuffle/tests/data/simplearchive.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..382144c7a0a9d7e2e611d62c7e6fa73f36072c1f GIT binary patch literal 197 zcmV;$06PC4iwFSRNP9~F1MSvL3d0}}hGEw|g(qk=@#g})h$&r^ED9!p9>389x@w^d zO=;d|Bf@|K@{NY$_!vc5YUYedtM*pqySq z_-g*=``1pj%>Npg|ND0Nwf~&J literal 0 HcmV?d00001 diff --git a/ark/kerfuffle/tests/jobstest.cpp b/ark/kerfuffle/tests/jobstest.cpp new file mode 100644 index 00000000..45fd77d8 --- /dev/null +++ b/ark/kerfuffle/tests/jobstest.cpp @@ -0,0 +1,366 @@ +/* + * Copyright (c) 2010-2011 Raphael Kubo da Costa + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ( INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION ) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "kerfuffle/jobs.h" + +#include "jsonarchiveinterface.h" + +#include +#include +#include + +#include +#include + +using Kerfuffle::ArchiveEntry; + +class JobsTest : public QObject +{ + Q_OBJECT + +public: + JobsTest(); + +protected Q_SLOTS: + void init(); + + void slotNewEntry(const ArchiveEntry& entry); + +private Q_SLOTS: + // ListJob-related tests + void testExtractedFilesSize(); + void testIsPasswordProtected(); + void testIsSingleFolderArchive(); + void testListEntries(); + + // ExtractJob-related tests + void testExtractJobAccessors(); + + // DeleteJob-related tests + void testRemoveEntry(); + + // AddJob-related tests + void testAddEntry(); + +private: + JSONArchiveInterface *createArchiveInterface(const QString& filePath); + QList listEntries(JSONArchiveInterface *iface); + void startAndWaitForResult(KJob *job); + + QList m_entries; + QEventLoop m_eventLoop; +}; + +QTEST_KDEMAIN_CORE(JobsTest) + +JobsTest::JobsTest() + : QObject(0) + , m_eventLoop(this) +{ + // Hackish way to make sure the i18n stuff + // is called from the main thread + KGlobal::locale(); + + qRegisterMetaType("ArchiveEntry"); +} + +void JobsTest::init() +{ + m_entries.clear(); +} + +JSONArchiveInterface *JobsTest::createArchiveInterface(const QString& filePath) +{ + QVariantList args; + args.append(filePath); + + JSONArchiveInterface *iface = new JSONArchiveInterface(this, args); + if (!iface->open()) { + kDebug() << "Could not open" << filePath; + return NULL; + } + + return iface; +} + +void JobsTest::startAndWaitForResult(KJob *job) +{ + connect(job, SIGNAL(result(KJob*)), &m_eventLoop, SLOT(quit())); + job->start(); + m_eventLoop.exec(); +} + +void JobsTest::testExtractedFilesSize() +{ + Kerfuffle::ListJob *listJob; + + JSONArchiveInterface *noSizeIface = + createArchiveInterface(QLatin1String(KDESRCDIR "data/archive001.json")); + JSONArchiveInterface *sizeIface = + createArchiveInterface(QLatin1String(KDESRCDIR "data/archive002.json")); + + listJob = new Kerfuffle::ListJob(noSizeIface, this); + listJob->setAutoDelete(false); + startAndWaitForResult(listJob); + + QCOMPARE(listJob->extractedFilesSize(), 0LL); + + listJob = new Kerfuffle::ListJob(sizeIface, this); + listJob->setAutoDelete(false); + startAndWaitForResult(listJob); + + QCOMPARE(listJob->extractedFilesSize(), 45959LL); + + noSizeIface->deleteLater(); + sizeIface->deleteLater(); +} + +void JobsTest::testIsPasswordProtected() +{ + Kerfuffle::ListJob *listJob; + + JSONArchiveInterface *noPasswordIface = + createArchiveInterface(QLatin1String(KDESRCDIR "data/archive002.json")); + JSONArchiveInterface *passwordIface = + createArchiveInterface(QLatin1String(KDESRCDIR "data/archive-password.json")); + + listJob = new Kerfuffle::ListJob(noPasswordIface, this); + listJob->setAutoDelete(false); + startAndWaitForResult(listJob); + + QVERIFY(!listJob->isPasswordProtected()); + + listJob = new Kerfuffle::ListJob(passwordIface, this); + listJob->setAutoDelete(false); + startAndWaitForResult(listJob); + + QVERIFY(listJob->isPasswordProtected()); + + noPasswordIface->deleteLater(); + passwordIface->deleteLater(); +} + +void JobsTest::testIsSingleFolderArchive() +{ + JSONArchiveInterface *iface = + createArchiveInterface(QLatin1String(KDESRCDIR "data/archive001.json")); + + Kerfuffle::ListJob *listJob = new Kerfuffle::ListJob(iface, this); + listJob->setAutoDelete(false); + startAndWaitForResult(listJob); + QVERIFY(!listJob->isSingleFolderArchive()); + QCOMPARE(listJob->subfolderName(), QString()); + iface->deleteLater(); + + iface = createArchiveInterface(QLatin1String(KDESRCDIR "data/archive-singlefile.json")); + listJob = new Kerfuffle::ListJob(iface, this); + listJob->setAutoDelete(false); + startAndWaitForResult(listJob); + QVERIFY(listJob->isSingleFolderArchive()); + QCOMPARE(listJob->subfolderName(), QLatin1String("a.txt")); + iface->deleteLater(); + + iface = createArchiveInterface(QLatin1String(KDESRCDIR "data/archive-onetopfolder.json")); + listJob = new Kerfuffle::ListJob(iface, this); + listJob->setAutoDelete(false); + startAndWaitForResult(listJob); + QVERIFY(listJob->isSingleFolderArchive()); + QCOMPARE(listJob->subfolderName(), QLatin1String("aDir")); + iface->deleteLater(); + + iface = createArchiveInterface + (QLatin1String(KDESRCDIR "data/archive-multiplefolders.json")); + listJob = new Kerfuffle::ListJob(iface, this); + listJob->setAutoDelete(false); + startAndWaitForResult(listJob); + QVERIFY(!listJob->isSingleFolderArchive()); + QCOMPARE(listJob->subfolderName(), QString()); + iface->deleteLater(); + + iface = createArchiveInterface + (QLatin1String(KDESRCDIR "data/archive-nodir-manyfiles.json")); + listJob = new Kerfuffle::ListJob(iface, this); + listJob->setAutoDelete(false); + startAndWaitForResult(listJob); + QVERIFY(!listJob->isSingleFolderArchive()); + QCOMPARE(listJob->subfolderName(), QString()); + iface->deleteLater(); + + iface = createArchiveInterface + (QLatin1String(KDESRCDIR "data/archive-deepsinglehierarchy.json")); + listJob = new Kerfuffle::ListJob(iface, this); + listJob->setAutoDelete(false); + startAndWaitForResult(listJob); + QVERIFY(listJob->isSingleFolderArchive()); + QCOMPARE(listJob->subfolderName(), QLatin1String("aDir")); + iface->deleteLater(); + + iface = createArchiveInterface + (QLatin1String(KDESRCDIR "data/archive-unorderedsinglefolder.json")); + listJob = new Kerfuffle::ListJob(iface, this); + listJob->setAutoDelete(false); + startAndWaitForResult(listJob); + QVERIFY(listJob->isSingleFolderArchive()); + QCOMPARE(listJob->subfolderName(), QLatin1String("aDir")); + iface->deleteLater(); +} + +void JobsTest::testListEntries() +{ + JSONArchiveInterface *iface = + createArchiveInterface(QLatin1String(KDESRCDIR "data/archive001.json")); + + QList archiveEntries(listEntries(iface)); + + QStringList entries; + entries.append(QLatin1String("a.txt")); + entries.append(QLatin1String("aDir/")); + entries.append(QLatin1String("aDir/b.txt")); + entries.append(QLatin1String("c.txt")); + + QCOMPARE(entries.count(), archiveEntries.count()); + + for (int i = 0; i < entries.count(); ++i) { + Kerfuffle::ArchiveEntry e(archiveEntries.at(i)); + + QCOMPARE(entries[i], e[Kerfuffle::FileName].toString()); + } + + iface->deleteLater(); +} + +void JobsTest::slotNewEntry(const ArchiveEntry& entry) +{ + m_entries.append(entry); +} + +QList JobsTest::listEntries(JSONArchiveInterface *iface) +{ + m_entries.clear(); + + Kerfuffle::ListJob *listJob = new Kerfuffle::ListJob(iface, this); + connect(listJob, SIGNAL(newEntry(ArchiveEntry)), + SLOT(slotNewEntry(ArchiveEntry))); + + startAndWaitForResult(listJob); + + return m_entries; +} + +void JobsTest::testExtractJobAccessors() +{ + JSONArchiveInterface *iface = createArchiveInterface(QLatin1String(KDESRCDIR "data/archive001.json")); + Kerfuffle::ExtractJob *job = + new Kerfuffle::ExtractJob(QVariantList(), QLatin1String("/tmp/some-dir"), + Kerfuffle::ExtractionOptions(), iface, this); + Kerfuffle::ExtractionOptions defaultOptions; + defaultOptions[QLatin1String("PreservePaths")] = false; + + QCOMPARE(job->destinationDirectory(), QLatin1String("/tmp/some-dir")); + QCOMPARE(job->extractionOptions(), defaultOptions); + + job->setAutoDelete(false); + startAndWaitForResult(job); + + QCOMPARE(job->destinationDirectory(), QLatin1String("/tmp/some-dir")); + QCOMPARE(job->extractionOptions(), defaultOptions); + + Kerfuffle::ExtractionOptions options; + options[QLatin1String("PreservePaths")] = true; + options[QLatin1String("foo")] = QLatin1String("bar"); + options[QLatin1String("pi")] = 3.14f; + + job = new Kerfuffle::ExtractJob(QVariantList(), QLatin1String("/root"), + options, iface, this); + + QCOMPARE(job->destinationDirectory(), QLatin1String("/root")); + QCOMPARE(job->extractionOptions(), options); + + job->setAutoDelete(false); + startAndWaitForResult(job); + + QCOMPARE(job->destinationDirectory(), QLatin1String("/root")); + QCOMPARE(job->extractionOptions(), options); +} + +void JobsTest::testRemoveEntry() +{ + QVariantList filesToDelete; + JSONArchiveInterface *iface; + Kerfuffle::DeleteJob *deleteJob; + QStringList expectedEntries; + + filesToDelete.append(QLatin1String("c.txt")); + iface = createArchiveInterface(QLatin1String(KDESRCDIR "data/archive001.json")); + deleteJob = new Kerfuffle::DeleteJob(filesToDelete, iface, this); + startAndWaitForResult(deleteJob); + QList archiveEntries(listEntries(iface)); + expectedEntries.append(QLatin1String("a.txt")); + expectedEntries.append(QLatin1String("aDir/")); + expectedEntries.append(QLatin1String("aDir/b.txt")); + QCOMPARE(archiveEntries.count(), expectedEntries.count()); + for (int i = 0; i < expectedEntries.count(); ++i) { + const Kerfuffle::ArchiveEntry e(archiveEntries.at(i)); + QCOMPARE(expectedEntries[i], e[Kerfuffle::FileName].toString()); + } + iface->deleteLater(); + + // TODO: test for errors +} + +void JobsTest::testAddEntry() +{ + JSONArchiveInterface *iface = createArchiveInterface(QLatin1String(KDESRCDIR "data/archive001.json")); + + QList archiveEntries = listEntries(iface); + QCOMPARE(archiveEntries.count(), 4); + + QStringList newEntries = QStringList() << QLatin1String("foo"); + + Kerfuffle::AddJob *addJob = + new Kerfuffle::AddJob(newEntries, Kerfuffle::CompressionOptions(), iface, this); + startAndWaitForResult(addJob); + + archiveEntries = listEntries(iface); + QCOMPARE(archiveEntries.count(), 5); + + addJob = new Kerfuffle::AddJob(newEntries, Kerfuffle::CompressionOptions(), iface, this); + startAndWaitForResult(addJob); + + archiveEntries = listEntries(iface); + QCOMPARE(archiveEntries.count(), 5); + + newEntries = QStringList() << QLatin1String("bar") << QLatin1String("aDir/test.txt"); + + addJob = new Kerfuffle::AddJob(newEntries, Kerfuffle::CompressionOptions(), iface, this); + startAndWaitForResult(addJob); + + archiveEntries = listEntries(iface); + QCOMPARE(archiveEntries.count(), 7); + + iface->deleteLater(); +} + +#include "jobstest.moc" diff --git a/ark/kerfuffle/tests/jsonarchiveinterface.cpp b/ark/kerfuffle/tests/jsonarchiveinterface.cpp new file mode 100644 index 00000000..14e62f44 --- /dev/null +++ b/ark/kerfuffle/tests/jsonarchiveinterface.cpp @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2010-2011 Raphael Kubo da Costa + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ( INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION ) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "jsonarchiveinterface.h" + +#include +#include + +#include + +JSONArchiveInterface::JSONArchiveInterface(QObject *parent, const QVariantList& args) + : Kerfuffle::ReadWriteArchiveInterface(parent, args) +{ +} + +JSONArchiveInterface::~JSONArchiveInterface() +{ +} + +bool JSONArchiveInterface::list() +{ + JSONParser::JSONArchive::const_iterator it = m_archive.constBegin(); + for (; it != m_archive.constEnd(); ++it) { + emit entry(*it); + } + + return true; +} + +bool JSONArchiveInterface::open() +{ + QFile file(filename()); + + if (!file.exists()) { + return false; + } + + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + return false; + } + + m_archive = JSONParser::parse(&file); + + return !m_archive.isEmpty(); +} + +bool JSONArchiveInterface::addFiles(const QStringList& files, const Kerfuffle::CompressionOptions& options) +{ + Q_UNUSED(options) + + foreach (const QString& file, files) { + if (m_archive.contains(file)) { + return false; + } + + Kerfuffle::ArchiveEntry e; + e[Kerfuffle::FileName] = file; + + m_archive[file] = e; + } + + return true; +} + +bool JSONArchiveInterface::copyFiles(const QList& files, const QString& destinationDirectory, Kerfuffle::ExtractionOptions options) +{ + Q_UNUSED(files) + Q_UNUSED(destinationDirectory) + Q_UNUSED(options) + + return true; +} + +bool JSONArchiveInterface::deleteFiles(const QList& files) +{ + foreach (const QVariant& file, files) { + const QString fileName = file.toString(); + if (m_archive.contains(fileName)) { + m_archive.remove(fileName); + emit entryRemoved(fileName); + } + } + + return true; +} + +#include "jsonarchiveinterface.moc" diff --git a/ark/kerfuffle/tests/jsonarchiveinterface.h b/ark/kerfuffle/tests/jsonarchiveinterface.h new file mode 100644 index 00000000..e74c4f1b --- /dev/null +++ b/ark/kerfuffle/tests/jsonarchiveinterface.h @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2010-2011 Raphael Kubo da Costa + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ( INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION ) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef JSONARCHIVEINTERFACE_H +#define JSONARCHIVEINTERFACE_H + +#include "jsonparser.h" + +#include "kerfuffle/archive.h" +#include "kerfuffle/archiveinterface.h" + +/** + * A dummy archive interface used by our test cases. + * + * It reads a JSON file which defines the contents of the archive. + * For the file format description, see the documentation for @c JSONParser. + * + * The file's content is read to memory when open() is called and the archive + * is then closed. This means that this class never changes the file's content + * on disk, and entry addition or deletion do not change the original file. + * + * @sa JSONParser + * + * @author Raphael Kubo da Costa + */ +class JSONArchiveInterface : public Kerfuffle::ReadWriteArchiveInterface +{ + Q_OBJECT + +public: + explicit JSONArchiveInterface(QObject *parent, const QVariantList& args); + virtual ~JSONArchiveInterface(); + + virtual bool list(); + virtual bool open(); + + virtual bool addFiles(const QStringList& files, const Kerfuffle::CompressionOptions& options); + virtual bool copyFiles(const QList& files, const QString& destinationDirectory, Kerfuffle::ExtractionOptions options); + virtual bool deleteFiles(const QList& files); + +private: + JSONParser::JSONArchive m_archive; +}; + +#endif diff --git a/ark/kerfuffle/tests/jsonparser.cpp b/ark/kerfuffle/tests/jsonparser.cpp new file mode 100644 index 00000000..2693f348 --- /dev/null +++ b/ark/kerfuffle/tests/jsonparser.cpp @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2011 Raphael Kubo da Costa + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ( INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION ) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "jsonparser.h" + +#include "kerfuffle/archiveinterface.h" + +#include + +#include + +#include + +typedef QMap ArchiveProperties; + +static ArchiveProperties archiveProperties() +{ + static ArchiveProperties properties; + + if (!properties.isEmpty()) { + return properties; + } + + properties[QLatin1String("FileName")] = Kerfuffle::FileName; + properties[QLatin1String("InternalID")] = Kerfuffle::InternalID; + properties[QLatin1String("Permissions")] = Kerfuffle::Permissions; + properties[QLatin1String("Owner")] = Kerfuffle::Owner; + properties[QLatin1String("Group")] = Kerfuffle::Group; + properties[QLatin1String("Size")] = Kerfuffle::Size; + properties[QLatin1String("CompressedSize")] = Kerfuffle::CompressedSize; + properties[QLatin1String("Link")] = Kerfuffle::Link; + properties[QLatin1String("Ratio")] = Kerfuffle::Ratio; + properties[QLatin1String("CRC")] = Kerfuffle::CRC; + properties[QLatin1String("Method")] = Kerfuffle::Method; + properties[QLatin1String("Version")] = Kerfuffle::Version; + properties[QLatin1String("Timestamp")] = Kerfuffle::Timestamp; + properties[QLatin1String("IsDirectory")] = Kerfuffle::IsDirectory; + properties[QLatin1String("Comment")] = Kerfuffle::Comment; + properties[QLatin1String("IsPasswordProtected")] = Kerfuffle::IsPasswordProtected; + + return properties; +} + +JSONParser::JSONParser() +{ +} + +JSONParser::~JSONParser() +{ +} + +JSONParser::JSONArchive JSONParser::parse(const QString &json) +{ + bool ok; + QJson::Parser parser; + + const QVariant result = parser.parse(json.toUtf8(), &ok); + + if (!ok) { + kDebug() << "Line" << parser.errorLine() << ":" << parser.errorString(); + return JSONParser::JSONArchive(); + } + + return createJSONArchive(result); +} + +JSONParser::JSONArchive JSONParser::parse(QIODevice *json) +{ + bool ok; + QJson::Parser parser; + + const QVariant result = parser.parse(json, &ok); + + if (!ok) { + kDebug() << "Line" << parser.errorLine() << ":" << parser.errorString(); + return JSONParser::JSONArchive(); + } + + return createJSONArchive(result); +} + +JSONParser::JSONArchive JSONParser::createJSONArchive(const QVariant &json) +{ + static const ArchiveProperties properties = archiveProperties(); + + JSONParser::JSONArchive archive; + + foreach (const QVariant &entry, json.toList()) { + const QVariantMap entryMap = entry.toMap(); + + if (!entryMap.contains(QLatin1String("FileName"))) { + continue; + } + + Kerfuffle::ArchiveEntry archiveEntry; + + QVariantMap::const_iterator entryIterator = entryMap.constBegin(); + for (; entryIterator != entryMap.constEnd(); ++entryIterator) { + if (properties.contains(entryIterator.key())) { + archiveEntry[properties[entryIterator.key()]] = entryIterator.value(); + } else { + kDebug() << entryIterator.key() << "is not a valid entry key"; + } + } + + const QString fileName = entryMap[QLatin1String("FileName")].toString(); + archive[fileName] = archiveEntry; + } + + return archive; +} diff --git a/ark/kerfuffle/tests/jsonparser.h b/ark/kerfuffle/tests/jsonparser.h new file mode 100644 index 00000000..91005dcd --- /dev/null +++ b/ark/kerfuffle/tests/jsonparser.h @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2011 Raphael Kubo da Costa + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ( INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION ) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef JSONPARSER_H +#define JSONPARSER_H + +#include "kerfuffle/archive.h" + +#include +#include +#include + +/** + * Simple parser which reads JSON files and creates @c ArchiveEntry objects + * from it. + * + * The JSON file is expected to follow a specific format that describes an + * archive read by Kerfuffle. + * + * The format consists of a list of dictionaries whose keys are values from the + * EntryMetaDataType enum. The only required key for each entry is FileName; + * other values which are omitted for each entry are assumed to be 0 or false. + * + * Example file: + * @code + * [ + * { "FileName": "foo", "IsPasswordProtected": true }, + * { "FileName": "aDir/", "IsDirectory": true } + * ] + * @endcode + * + * @author Raphael Kubo da Costa + */ +class JSONParser +{ +public: + typedef QMap JSONArchive; + + ~JSONParser(); + + static JSONArchive parse(const QString &json); + static JSONArchive parse(QIODevice *json); + +private: + JSONParser(); + + /** + * Parses each entry in the QVariant obtained from parsing a JSON file and + * creates a @c JSONArchive from them. + * + * If an entry does not have a "FileName" key, it is ignored. Keys which do + * not correspond to a value in the EntryMetaDataType enum are ignored. + * + * @return A new @c JSONArchive corresponding to the parsed JSON file. If a + * parsing error occurs, it is empty. + */ + static JSONArchive createJSONArchive(const QVariant &json); +}; + +#endif // JSONPARSER_H diff --git a/ark/part/CMakeLists.txt b/ark/part/CMakeLists.txt new file mode 100644 index 00000000..a75d9b26 --- /dev/null +++ b/ark/part/CMakeLists.txt @@ -0,0 +1,30 @@ +set(arkpart_PART_SRCS + part.cpp + infopanel.cpp + arkviewer.cpp + archivemodel.cpp + archiveview.cpp + jobtracker.cpp + ) + +qt4_add_dbus_adaptor(arkpart_PART_SRCS dnddbusinterface.xml part.h Ark::Part) + +kde4_add_ui_files(arkpart_PART_SRCS infopanel.ui ) +kde4_add_ui_files(arkpart_PART_SRCS jobtracker.ui ) + +kde4_add_plugin(arkpart ${arkpart_PART_SRCS}) + +target_link_libraries(arkpart kerfuffle ${KDE4_KFILE_LIBS} ${KDE4_KPARTS_LIBS} ${KDE4_KDEUI_LIBS}) + +configure_file( + ${CMAKE_CURRENT_SOURCE_DIR}/ark_part.desktop.cmake + ${CMAKE_CURRENT_BINARY_DIR}/ark_part.desktop +) + +install(TARGETS arkpart DESTINATION ${PLUGIN_INSTALL_DIR}) + + +########### install files ############### + +install( FILES ${CMAKE_CURRENT_BINARY_DIR}/ark_part.desktop DESTINATION ${SERVICES_INSTALL_DIR} ) +install( FILES ark_part.rc DESTINATION ${DATA_INSTALL_DIR}/ark ) diff --git a/ark/part/archivemodel.cpp b/ark/part/archivemodel.cpp new file mode 100644 index 00000000..43262689 --- /dev/null +++ b/ark/part/archivemodel.cpp @@ -0,0 +1,986 @@ +/* + * ark -- archiver for the KDE project + * + * Copyright (C) 2007 Henrique Pinto + * Copyright (C) 2008-2009 Harald Hvaal + * Copyright (C) 2010-2012 Raphael Kubo da Costa + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "archivemodel.h" +#include "kerfuffle/archive.h" +#include "kerfuffle/jobs.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace Kerfuffle; + +class ArchiveDirNode; + +//used to speed up the loading of large archives +static ArchiveNode* s_previousMatch = NULL; +K_GLOBAL_STATIC(QStringList, s_previousPieces) + + +// TODO: This class hierarchy needs some love. +// Having a parent take a child class as a parameter in the constructor +// should trigger one's spider-sense (TM). +class ArchiveNode +{ +public: + ArchiveNode(ArchiveDirNode *parent, const ArchiveEntry & entry) + : m_parent(parent) + { + setEntry(entry); + } + + virtual ~ArchiveNode() + { + } + + const ArchiveEntry &entry() const + { + return m_entry; + } + + void setEntry(const ArchiveEntry& entry) + { + m_entry = entry; + + const QStringList pieces = entry[FileName].toString().split(QLatin1Char( '/' ), QString::SkipEmptyParts); + m_name = pieces.isEmpty() ? QString() : pieces.last(); + + if (entry[IsDirectory].toBool()) { + m_icon = KIconLoader::global()->loadMimeTypeIcon(KMimeType::mimeType(QLatin1String("inode/directory"))->iconName(), KIconLoader::Small); + } else { + const KMimeType::Ptr mimeType = KMimeType::findByPath(m_entry[FileName].toString(), 0, true); + m_icon = KIconLoader::global()->loadMimeTypeIcon(mimeType->iconName(), KIconLoader::Small); + } + } + + ArchiveDirNode *parent() const + { + return m_parent; + } + + int row() const; + + virtual bool isDir() const + { + return false; + } + + QPixmap icon() const + { + return m_icon; + } + + QString name() const + { + return m_name; + } + +protected: + void setIcon(const QPixmap &icon) + { + m_icon = icon; + } + +private: + ArchiveEntry m_entry; + QPixmap m_icon; + QString m_name; + ArchiveDirNode *m_parent; +}; + + +class ArchiveDirNode: public ArchiveNode +{ +public: + ArchiveDirNode(ArchiveDirNode *parent, const ArchiveEntry & entry) + : ArchiveNode(parent, entry) + { + } + + ~ArchiveDirNode() + { + clear(); + } + + QList entries() + { + return m_entries; + } + + void setEntryAt(int index, ArchiveNode* value) + { + m_entries[index] = value; + } + + void appendEntry(ArchiveNode* entry) + { + m_entries.append(entry); + } + + void removeEntryAt(int index) + { + delete m_entries.takeAt(index); + } + + virtual bool isDir() const + { + return true; + } + + ArchiveNode* find(const QString & name) + { + foreach(ArchiveNode *node, m_entries) { + if (node && (node->name() == name)) { + return node; + } + } + return 0; + } + + ArchiveNode* findByPath(const QStringList & pieces, int index = 0) + { + if (index == pieces.count()) { + return 0; + } + + ArchiveNode *next = find(pieces.at(index)); + + if (index == pieces.count() - 1) { + return next; + } + if (next && next->isDir()) { + return static_cast(next)->findByPath(pieces, index + 1); + } + return 0; + } + + void returnDirNodes(QList *store) + { + foreach(ArchiveNode *node, m_entries) { + if (node->isDir()) { + store->prepend(static_cast(node)); + static_cast(node)->returnDirNodes(store); + } + } + } + + void clear() + { + qDeleteAll(m_entries); + m_entries.clear(); + } + +private: + QList m_entries; +}; + +/** + * Helper functor used by qStableSort. + * + * It always sorts folders before files. + * + * @internal + */ +class ArchiveModelSorter +{ +public: + ArchiveModelSorter(int column, Qt::SortOrder order) + : m_sortColumn(column) + , m_sortOrder(order) + { + } + + virtual ~ArchiveModelSorter() + { + } + + inline bool operator()(const QPair &left, const QPair &right) const + { + if (m_sortOrder == Qt::AscendingOrder) { + return lessThan(left, right); + } else { + return !lessThan(left, right); + } + } + +protected: + bool lessThan(const QPair &left, const QPair &right) const + { + const ArchiveNode * const leftNode = left.first; + const ArchiveNode * const rightNode = right.first; + + // #234373: sort folders before files + if ((leftNode->isDir()) && (!rightNode->isDir())) { + return (m_sortOrder == Qt::AscendingOrder); + } else if ((!leftNode->isDir()) && (rightNode->isDir())) { + return !(m_sortOrder == Qt::AscendingOrder); + } + + const QVariant &leftEntry = leftNode->entry()[m_sortColumn]; + const QVariant &rightEntry = rightNode->entry()[m_sortColumn]; + + switch (m_sortColumn) { + case FileName: + return leftNode->name() < rightNode->name(); + case Size: + case CompressedSize: + return leftEntry.toInt() < rightEntry.toInt(); + default: + return leftEntry.toString() < rightEntry.toString(); + } + + // We should not get here. + Q_ASSERT(false); + return false; + } + +private: + int m_sortColumn; + Qt::SortOrder m_sortOrder; +}; + +int ArchiveNode::row() const +{ + if (parent()) { + return parent()->entries().indexOf(const_cast(this)); + } + return 0; +} + +ArchiveModel::ArchiveModel(const QString &dbusPathName, QObject *parent) + : QAbstractItemModel(parent) + , m_rootNode(new ArchiveDirNode(0, ArchiveEntry())) + , m_dbusPathName(dbusPathName) +{ +} + +ArchiveModel::~ArchiveModel() +{ + delete m_rootNode; + m_rootNode = 0; +} + +QVariant ArchiveModel::data(const QModelIndex &index, int role) const +{ + if (index.isValid()) { + ArchiveNode *node = static_cast(index.internalPointer()); + switch (role) { + case Qt::DisplayRole: { + //TODO: complete the columns + int columnId = m_showColumns.at(index.column()); + switch (columnId) { + case FileName: + return node->name(); + case Size: + if (node->isDir()) { + int dirs; + int files; + const int children = childCount(index, dirs, files); + return KIO::itemsSummaryString(children, files, dirs, 0, false); + } else if (node->entry().contains(Link)) { + return QVariant(); + } else { + return KIO::convertSize(node->entry()[ Size ].toULongLong()); + } + case CompressedSize: + if (node->isDir() || node->entry().contains(Link)) { + return QVariant(); + } else { + qulonglong compressedSize = node->entry()[ CompressedSize ].toULongLong(); + if (compressedSize != 0) { + return KIO::convertSize(compressedSize); + } else { + return QVariant(); + } + } + case Ratio: // TODO: Use node->entry()[Ratio] when available + if (node->isDir() || node->entry().contains(Link)) { + return QVariant(); + } else { + qulonglong compressedSize = node->entry()[ CompressedSize ].toULongLong(); + qulonglong size = node->entry()[ Size ].toULongLong(); + if (compressedSize == 0 || size == 0) { + return QVariant(); + } else { + int ratio = int(100 * ((double)size - compressedSize) / size); + return QString(QString::number(ratio) + QLatin1String( " %" )); + } + } + + case Timestamp: { + const QDateTime timeStamp = node->entry().value(Timestamp).toDateTime(); + return KGlobal::locale()->formatDateTime(timeStamp); + } + + default: + return node->entry().value(columnId); + } + break; + } + case Qt::DecorationRole: + if (index.column() == 0) { + return node->icon(); + } + return QVariant(); + case Qt::FontRole: { + QFont f; + f.setItalic(node->entry()[ IsPasswordProtected ].toBool()); + return f; + } + default: + return QVariant(); + } + } + return QVariant(); +} + +Qt::ItemFlags ArchiveModel::flags(const QModelIndex &index) const +{ + Qt::ItemFlags defaultFlags = QAbstractItemModel::flags(index); + + if (index.isValid()) { + return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | defaultFlags; + } + + return 0; +} + +QVariant ArchiveModel::headerData(int section, Qt::Orientation, int role) const +{ + if (role == Qt::DisplayRole) { + if (section >= m_showColumns.size()) { + kDebug() << "WEIRD: showColumns.size = " << m_showColumns.size() + << " and section = " << section; + return QVariant(); + } + + int columnId = m_showColumns.at(section); + + switch (columnId) { + case FileName: + return i18nc("Name of a file inside an archive", "Name"); + case Size: + return i18nc("Uncompressed size of a file inside an archive", "Size"); + case CompressedSize: + return i18nc("Compressed size of a file inside an archive", "Compressed"); + case Ratio: + return i18nc("Compression rate of file", "Rate"); + case Owner: + return i18nc("File's owner username", "Owner"); + case Group: + return i18nc("File's group", "Group"); + case Permissions: + return i18nc("File permissions", "Mode"); + case CRC: + return i18nc("CRC hash code", "CRC"); + case Method: + return i18nc("Compression method", "Method"); + case Version: + //TODO: what exactly is a file version? + return i18nc("File version", "Version"); + case Timestamp: + return i18nc("Timestamp", "Date"); + case Comment: + return i18nc("File comment", "Comment"); + default: + return i18nc("Unnamed column", "??"); + + } + } + return QVariant(); +} + +QModelIndex ArchiveModel::index(int row, int column, const QModelIndex &parent) const +{ + if (hasIndex(row, column, parent)) { + ArchiveDirNode *parentNode = parent.isValid() ? static_cast(parent.internalPointer()) : m_rootNode; + + Q_ASSERT(parentNode->isDir()); + + ArchiveNode *item = parentNode->entries().value(row, 0); + if (item) { + return createIndex(row, column, item); + } + } + + return QModelIndex(); +} + +QModelIndex ArchiveModel::parent(const QModelIndex &index) const +{ + if (index.isValid()) { + ArchiveNode *item = static_cast(index.internalPointer()); + Q_ASSERT(item); + if (item->parent() && (item->parent() != m_rootNode)) { + return createIndex(item->parent()->row(), 0, item->parent()); + } + } + return QModelIndex(); +} + +ArchiveEntry ArchiveModel::entryForIndex(const QModelIndex &index) +{ + if (index.isValid()) { + ArchiveNode *item = static_cast(index.internalPointer()); + Q_ASSERT(item); + return item->entry(); + } + return ArchiveEntry(); +} + +int ArchiveModel::childCount(const QModelIndex &index, int &dirs, int &files) const +{ + if (index.isValid()) { + dirs = files = 0; + ArchiveNode *item = static_cast(index.internalPointer()); + Q_ASSERT(item); + if (item->isDir()) { + const QList entries = static_cast(item)->entries(); + foreach(const ArchiveNode *node, entries) { + if (node->isDir()) { + dirs++; + } else { + files++; + } + } + return entries.count(); + } + return 0; + } + return -1; +} + +int ArchiveModel::rowCount(const QModelIndex &parent) const +{ + if (parent.column() <= 0) { + ArchiveNode *parentNode = parent.isValid() ? static_cast(parent.internalPointer()) : m_rootNode; + + if (parentNode && parentNode->isDir()) { + return static_cast(parentNode)->entries().count(); + } + } + return 0; +} + +int ArchiveModel::columnCount(const QModelIndex &parent) const +{ + return m_showColumns.size(); + if (parent.isValid()) { + return static_cast(parent.internalPointer())->entry().size(); + } +} + +void ArchiveModel::sort(int column, Qt::SortOrder order) +{ + if (m_showColumns.size() <= column) { + return; + } + + emit layoutAboutToBeChanged(); + + QList dirNodes; + m_rootNode->returnDirNodes(&dirNodes); + dirNodes.append(m_rootNode); + + const ArchiveModelSorter modelSorter(m_showColumns.at(column), order); + + foreach(ArchiveDirNode* dir, dirNodes) { + QVector < QPair > sorting(dir->entries().count()); + for (int i = 0; i < dir->entries().count(); ++i) { + ArchiveNode *item = dir->entries().at(i); + sorting[i].first = item; + sorting[i].second = i; + } + + qStableSort(sorting.begin(), sorting.end(), modelSorter); + + QModelIndexList fromIndexes; + QModelIndexList toIndexes; + for (int r = 0; r < sorting.count(); ++r) { + ArchiveNode *item = sorting.at(r).first; + toIndexes.append(createIndex(r, 0, item)); + fromIndexes.append(createIndex(sorting.at(r).second, 0, sorting.at(r).first)); + dir->setEntryAt(r, sorting.at(r).first); + } + + changePersistentIndexList(fromIndexes, toIndexes); + + emit dataChanged( + index(0, 0, indexForNode(dir)), + index(dir->entries().size() - 1, 0, indexForNode(dir))); + } + + emit layoutChanged(); +} + +Qt::DropActions ArchiveModel::supportedDropActions() const +{ + return Qt::CopyAction | Qt::MoveAction; +} + +QStringList ArchiveModel::mimeTypes() const +{ + QStringList types; + + // MIME types we accept for dragging (eg. Dolphin -> Ark). + types << QLatin1String("text/uri-list") + << QLatin1String("text/plain") + << QLatin1String("text/x-moz-url"); + + // MIME types we accept for dropping (eg. Ark -> Dolphin). + types << QLatin1String("application/x-kde-ark-dndextract-service") + << QLatin1String("application/x-kde-ark-dndextract-path"); + + return types; +} + +QMimeData *ArchiveModel::mimeData(const QModelIndexList &indexes) const +{ + Q_UNUSED(indexes) + + QMimeData *mimeData = new QMimeData; + mimeData->setData(QLatin1String("application/x-kde-ark-dndextract-service"), + QDBusConnection::sessionBus().baseService().toUtf8()); + mimeData->setData(QLatin1String("application/x-kde-ark-dndextract-path"), + m_dbusPathName.toUtf8()); + + return mimeData; +} + +bool ArchiveModel::dropMimeData(const QMimeData * data, Qt::DropAction action, int row, int column, const QModelIndex & parent) +{ + Q_UNUSED(action) + Q_UNUSED(row) + Q_UNUSED(column) + Q_UNUSED(parent) + + if (!data->hasUrls()) { + return false; + } + + QStringList paths; + foreach(const QUrl &url, data->urls()) { + paths << url.toLocalFile(); + } + + //for now, this code is not used because adding files to paths inside the + //archive is not supported yet. need a solution for this later. + QString path; +#if 0 + if (parent.isValid()) { + QModelIndex droppedOnto = index(row, column, parent); + if (entryForIndex(droppedOnto).value(IsDirectory).toBool()) { + kDebug() << "Using entry"; + path = entryForIndex(droppedOnto).value(FileName).toString(); + } else { + path = entryForIndex(parent).value(FileName).toString(); + } + } + + kDebug() << "Dropped onto " << path; + +#endif + + emit droppedFiles(paths, path); + + return true; +} + +// For a rationale, see bugs #194241 and #241967 +QString ArchiveModel::cleanFileName(const QString& fileName) +{ + if ((fileName == QLatin1String("/")) || + (fileName == QLatin1String("."))) { // "." is present in ISO files + return QString(); + } else if (fileName.startsWith(QLatin1String("./"))) { + return fileName.mid(2); + } + + return fileName; +} + +ArchiveDirNode* ArchiveModel::parentFor(const ArchiveEntry& entry) +{ + QStringList pieces = entry[ FileName ].toString().split(QLatin1Char( '/' ), QString::SkipEmptyParts); + if (pieces.isEmpty()) { + return NULL; + } + pieces.removeLast(); + + if (s_previousMatch) { + //the number of path elements must be the same for the shortcut + //to work + if (s_previousPieces->count() == pieces.count()) { + bool equal = true; + + //make sure all the pieces match up + for (int i = 0; i < s_previousPieces->count(); ++i) { + if (s_previousPieces->at(i) != pieces.at(i)) { + equal = false; + break; + } + } + + //if match return it + if (equal) { + return static_cast(s_previousMatch); + } + } + } + + ArchiveDirNode *parent = m_rootNode; + + foreach(const QString &piece, pieces) { + ArchiveNode *node = parent->find(piece); + if (!node) { + ArchiveEntry e; + e[ FileName ] = (parent == m_rootNode) ? + piece : parent->entry()[ FileName ].toString() + QLatin1Char( '/' ) + piece; + e[ IsDirectory ] = true; + node = new ArchiveDirNode(parent, e); + insertNode(node); + } + if (!node->isDir()) { + ArchiveEntry e(node->entry()); + node = new ArchiveDirNode(parent, e); + //Maybe we have both a file and a directory of the same name + // We avoid removing previous entries unless necessary + insertNode(node); + } + parent = static_cast(node); + } + + s_previousMatch = parent; + *s_previousPieces = pieces; + + return parent; +} +QModelIndex ArchiveModel::indexForNode(ArchiveNode *node) +{ + Q_ASSERT(node); + if (node != m_rootNode) { + Q_ASSERT(node->parent()); + Q_ASSERT(node->parent()->isDir()); + return createIndex(node->row(), 0, node); + } + return QModelIndex(); +} + +void ArchiveModel::slotEntryRemoved(const QString & path) +{ + kDebug() << "Removed node at path " << path; + + const QString entryFileName(cleanFileName(path)); + if (entryFileName.isEmpty()) { + return; + } + + ArchiveNode *entry = m_rootNode->findByPath(entryFileName.split(QLatin1Char( '/' ), QString::SkipEmptyParts)); + if (entry) { + ArchiveDirNode *parent = entry->parent(); + QModelIndex index = indexForNode(entry); + + beginRemoveRows(indexForNode(parent), entry->row(), entry->row()); + + //delete parent->entries()[ entry->row() ]; + //parent->entries()[ entry->row() ] = 0; + parent->removeEntryAt(entry->row()); + + endRemoveRows(); + } else { + kDebug() << "Did not find the removed node"; + } +} + +void ArchiveModel::slotUserQuery(Kerfuffle::Query *query) +{ + query->execute(); +} + +void ArchiveModel::slotNewEntryFromSetArchive(const ArchiveEntry& entry) +{ + // we cache all entries that appear when opening a new archive + // so we can all them together once it's done, this is a huge + // performance improvement because we save from doing lots of + // begin/endInsertRows + m_newArchiveEntries.push_back(entry); +} + +void ArchiveModel::slotNewEntry(const ArchiveEntry& entry) +{ + newEntry(entry, NotifyViews); +} + +void ArchiveModel::newEntry(const ArchiveEntry& receivedEntry, InsertBehaviour behaviour) +{ + if (receivedEntry[FileName].toString().isEmpty()) { + kDebug() << "Weird, received empty entry (no filename) - skipping"; + return; + } + + //if there are no addidional columns registered, then have a look at the + //entry and populate some + if (m_showColumns.isEmpty()) { + //these are the columns we are interested in showing in the display + static const QList columnsForDisplay = + QList() + << FileName + << Size + << CompressedSize + << Permissions + << Owner + << Group + << Ratio + << CRC + << Method + << Version + << Timestamp + << Comment; + + QList toInsert; + + foreach(int column, columnsForDisplay) { + if (receivedEntry.contains(column)) { + toInsert << column; + } + } + beginInsertColumns(QModelIndex(), 0, toInsert.size() - 1); + m_showColumns << toInsert; + endInsertColumns(); + + kDebug() << "Show columns detected: " << m_showColumns; + } + + //make a copy + ArchiveEntry entry = receivedEntry; + + //#194241: Filenames such as "./file" should be displayed as "file" + //#241967: Entries called "/" should be ignored + QString entryFileName = cleanFileName(entry[FileName].toString()); + if (entryFileName.isEmpty()) { // The entry contains only "." or "./" + return; + } + entry[FileName] = entryFileName; + + /// 1. Skip already created nodes + if (m_rootNode) { + ArchiveNode *existing = m_rootNode->findByPath(entry[ FileName ].toString().split(QLatin1Char( '/' ))); + if (existing) { + kDebug() << "Refreshing entry for" << entry[FileName].toString(); + + // Multi-volume files are repeated at least in RAR archives. + // In that case, we need to sum the compressed size for each volume + qulonglong currentCompressedSize = existing->entry()[CompressedSize].toULongLong(); + entry[CompressedSize] = currentCompressedSize + entry[CompressedSize].toULongLong(); + + //TODO: benchmark whether it's a bad idea to reset the entry here. + existing->setEntry(entry); + return; + } + } + + /// 2. Find Parent Node, creating missing ArchiveDirNodes in the process + ArchiveDirNode *parent = parentFor(entry); + + /// 3. Create an ArchiveNode + QString name = entry[ FileName ].toString().split(QLatin1Char( '/' ), QString::SkipEmptyParts).last(); + ArchiveNode *node = parent->find(name); + if (node) { + node->setEntry(entry); + } else { + if (entry[ FileName ].toString().endsWith(QLatin1Char( '/' )) || (entry.contains(IsDirectory) && entry[ IsDirectory ].toBool())) { + node = new ArchiveDirNode(parent, entry); + } else { + node = new ArchiveNode(parent, entry); + } + insertNode(node, behaviour); + } +} + +void ArchiveModel::slotLoadingFinished(KJob *job) +{ + //kDebug() << entry; + foreach(const ArchiveEntry &entry, m_newArchiveEntries) { + newEntry(entry, DoNotNotifyViews); + } + reset(); + m_newArchiveEntries.clear(); + + emit loadingFinished(job); +} + +void ArchiveModel::insertNode(ArchiveNode *node, InsertBehaviour behaviour) +{ + Q_ASSERT(node); + ArchiveDirNode *parent = node->parent(); + Q_ASSERT(parent); + if (behaviour == NotifyViews) { + beginInsertRows(indexForNode(parent), parent->entries().count(), parent->entries().count()); + } + parent->appendEntry(node); + if (behaviour == NotifyViews) { + endInsertRows(); + } +} + +Kerfuffle::Archive* ArchiveModel::archive() const +{ + return m_archive.data(); +} + +KJob* ArchiveModel::setArchive(Kerfuffle::Archive *archive) +{ + m_archive.reset(archive); + + m_rootNode->clear(); + s_previousMatch = 0; + s_previousPieces->clear(); + + Kerfuffle::ListJob *job = NULL; + + m_newArchiveEntries.clear(); + if (m_archive) { + job = m_archive->list(); // TODO: call "open" or "create"? + + connect(job, SIGNAL(newEntry(ArchiveEntry)), + this, SLOT(slotNewEntryFromSetArchive(ArchiveEntry))); + + connect(job, SIGNAL(result(KJob*)), + this, SLOT(slotLoadingFinished(KJob*))); + + connect(job, SIGNAL(userQuery(Kerfuffle::Query*)), + this, SLOT(slotUserQuery(Kerfuffle::Query*))); + + emit loadingStarted(); + + // TODO: make sure if it's ok to not have calls to beginRemoveColumns here + m_showColumns.clear(); + } + reset(); + return job; +} + +ExtractJob* ArchiveModel::extractFile(const QVariant& fileName, const QString & destinationDir, const Kerfuffle::ExtractionOptions options) const +{ + QList files; + files << fileName; + return extractFiles(files, destinationDir, options); +} + +ExtractJob* ArchiveModel::extractFiles(const QList& files, const QString & destinationDir, const Kerfuffle::ExtractionOptions options) const +{ + Q_ASSERT(m_archive); + ExtractJob *newJob = m_archive->copyFiles(files, destinationDir, options); + connect(newJob, SIGNAL(userQuery(Kerfuffle::Query*)), + this, SLOT(slotUserQuery(Kerfuffle::Query*))); + return newJob; +} + +AddJob* ArchiveModel::addFiles(const QStringList & filenames, const CompressionOptions& options) +{ + if (!m_archive) { + return NULL; + } + + if (!m_archive->isReadOnly()) { + AddJob *job = m_archive->addFiles(filenames, options); + connect(job, SIGNAL(newEntry(ArchiveEntry)), + this, SLOT(slotNewEntry(ArchiveEntry))); + connect(job, SIGNAL(userQuery(Kerfuffle::Query*)), + this, SLOT(slotUserQuery(Kerfuffle::Query*))); + + + return job; + } + return 0; +} + +DeleteJob* ArchiveModel::deleteFiles(const QList & files) +{ + Q_ASSERT(m_archive); + if (!m_archive->isReadOnly()) { + DeleteJob *job = m_archive->deleteFiles(files); + connect(job, SIGNAL(entryRemoved(QString)), + this, SLOT(slotEntryRemoved(QString))); + + connect(job, SIGNAL(finished(KJob*)), + this, SLOT(slotCleanupEmptyDirs())); + + connect(job, SIGNAL(userQuery(Kerfuffle::Query*)), + this, SLOT(slotUserQuery(Kerfuffle::Query*))); + return job; + } + return 0; +} + +void ArchiveModel::slotCleanupEmptyDirs() +{ + kDebug(); + QList queue; + QList nodesToDelete; + + //add root nodes + for (int i = 0; i < rowCount(); ++i) { + queue.append(QPersistentModelIndex(index(i, 0))); + } + + //breadth-first traverse + while (!queue.isEmpty()) { + QPersistentModelIndex node = queue.takeFirst(); + ArchiveEntry entry = entryForIndex(node); + //kDebug() << "Trying " << entry[FileName].toString(); + + if (!hasChildren(node)) { + if (!entry.contains(InternalID)) { + nodesToDelete << node; + } + } else { + for (int i = 0; i < rowCount(node); ++i) { + queue.append(QPersistentModelIndex(index(i, 0, node))); + } + } + } + + foreach(const QPersistentModelIndex& node, nodesToDelete) { + ArchiveNode *rawNode = static_cast(node.internalPointer()); + kDebug() << "Delete with parent entries " << rawNode->parent()->entries() << " and row " << rawNode->row(); + beginRemoveRows(parent(node), rawNode->row(), rawNode->row()); + rawNode->parent()->removeEntryAt(rawNode->row()); + endRemoveRows(); + //kDebug() << "Removed entry " << entry[FileName].toString(); + } +} + +#include "archivemodel.moc" diff --git a/ark/part/archivemodel.h b/ark/part/archivemodel.h new file mode 100644 index 00000000..7f8c527f --- /dev/null +++ b/ark/part/archivemodel.h @@ -0,0 +1,126 @@ +/* + * ark -- archiver for the KDE project + * + * Copyright (C) 2007 Henrique Pinto + * Copyright (C) 2008-2009 Harald Hvaal + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ +#ifndef ARCHIVEMODEL_H +#define ARCHIVEMODEL_H + +#include +#include + +#include +#include "kerfuffle/archive.h" + +using Kerfuffle::ArchiveEntry; + +namespace Kerfuffle +{ + class Query; +} + +class ArchiveNode; +class ArchiveDirNode; + +class ArchiveModel: public QAbstractItemModel +{ + Q_OBJECT +public: + ArchiveModel(const QString &dbusPathName, QObject *parent = 0); + ~ArchiveModel(); + + QVariant data(const QModelIndex &index, int role) const; + Qt::ItemFlags flags(const QModelIndex &index) const; + QVariant headerData(int section, Qt::Orientation orientation, + int role = Qt::DisplayRole) const; + QModelIndex index(int row, int column, + const QModelIndex &parent = QModelIndex()) const; + QModelIndex parent(const QModelIndex &index) const; + int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; + + virtual void sort(int column, Qt::SortOrder order = Qt::AscendingOrder); + + //drag and drop related + virtual Qt::DropActions supportedDropActions() const; + virtual QStringList mimeTypes() const; + virtual QMimeData * mimeData(const QModelIndexList & indexes) const; + + virtual bool dropMimeData(const QMimeData * data, Qt::DropAction action, int row, int column, const QModelIndex & parent); + + KJob* setArchive(Kerfuffle::Archive *archive); + Kerfuffle::Archive *archive() const; + + Kerfuffle::ArchiveEntry entryForIndex(const QModelIndex &index); + int childCount(const QModelIndex &index, int &dirs, int &files) const; + + Kerfuffle::ExtractJob* extractFile(const QVariant& fileName, const QString & destinationDir, const Kerfuffle::ExtractionOptions options = Kerfuffle::ExtractionOptions()) const; + Kerfuffle::ExtractJob* extractFiles(const QList& files, const QString & destinationDir, const Kerfuffle::ExtractionOptions options = Kerfuffle::ExtractionOptions()) const; + + Kerfuffle::AddJob* addFiles(const QStringList & paths, const Kerfuffle::CompressionOptions& options = Kerfuffle::CompressionOptions()); + Kerfuffle::DeleteJob* deleteFiles(const QList & files); + +signals: + void loadingStarted(); + void loadingFinished(KJob *); + void extractionFinished(bool success); + void error(const QString& error, const QString& details); + void droppedFiles(const QStringList& files, const QString& path = QString()); + +private slots: + void slotNewEntryFromSetArchive(const ArchiveEntry& entry); + void slotNewEntry(const ArchiveEntry& entry); + void slotLoadingFinished(KJob *job); + void slotEntryRemoved(const QString & path); + void slotUserQuery(Kerfuffle::Query *query); + void slotCleanupEmptyDirs(); + +private: + /** + * Strips file names that start with './'. + * + * For more information, see bug 194241. + * + * @param fileName The file name that will be stripped. + * + * @return @p fileName without the leading './' + */ + QString cleanFileName(const QString& fileName); + + ArchiveDirNode* parentFor(const Kerfuffle::ArchiveEntry& entry); + QModelIndex indexForNode(ArchiveNode *node); + static bool compareAscending(const QModelIndex& a, const QModelIndex& b); + static bool compareDescending(const QModelIndex& a, const QModelIndex& b); + /** + * Insert the node @p node into the model, ensuring all views are notified + * of the change. + */ + enum InsertBehaviour { NotifyViews, DoNotNotifyViews }; + void insertNode(ArchiveNode *node, InsertBehaviour behaviour = NotifyViews); + void newEntry(const Kerfuffle::ArchiveEntry& entry, InsertBehaviour behaviour); + + QList m_newArchiveEntries; // holds entries from opening a new archive until it's totally open + QList m_showColumns; + QScopedPointer m_archive; + ArchiveDirNode *m_rootNode; + + QString m_dbusPathName; +}; + +#endif // ARCHIVEMODEL_H diff --git a/ark/part/archiveview.cpp b/ark/part/archiveview.cpp new file mode 100644 index 00000000..6b9918df --- /dev/null +++ b/ark/part/archiveview.cpp @@ -0,0 +1,142 @@ +/* + * ark -- archiver for the KDE project + * + * Copyright (C) 2008-2009 Harald Hvaal + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "archiveview.h" + +#include +#include + +#include +#include +#include +#include + +ArchiveView::ArchiveView(QWidget *parent) + : QTreeView(parent) + , m_mouseButtons(Qt::NoButton) +{ + connect(this, SIGNAL(pressed(QModelIndex)), + SLOT(updateMouseButtons())); + connect(this, SIGNAL(clicked(QModelIndex)), + SLOT(slotClicked(QModelIndex))); + connect(this, SIGNAL(doubleClicked(QModelIndex)), + SLOT(slotDoubleClicked(QModelIndex))); +} + +// FIXME: this is a workaround taken from Dolphin until QTBUG-1067 is resolved +void ArchiveView::updateMouseButtons() +{ + m_mouseButtons = QApplication::mouseButtons(); +} + +void ArchiveView::slotClicked(const QModelIndex& index) +{ + if (KGlobalSettings::singleClick()) { + if (m_mouseButtons != Qt::LeftButton) { // FIXME: see QTBUG-1067 + return; + } + + // If the user is pressing shift or control, more than one item is being selected + const Qt::KeyboardModifiers modifier = QApplication::keyboardModifiers(); + if ((modifier & Qt::ShiftModifier) || (modifier & Qt::ControlModifier)) { + return; + } + + emit itemTriggered(index); + } +} + +void ArchiveView::slotDoubleClicked(const QModelIndex& index) +{ + if (!KGlobalSettings::singleClick()) { + emit itemTriggered(index); + } +} + +void ArchiveView::setModel(QAbstractItemModel *model) +{ + kDebug(); + QTreeView::setModel(model); + setSelectionMode(QAbstractItemView::ExtendedSelection); + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + setAlternatingRowColors(true); + setAnimated(true); + setAllColumnsShowFocus(true); + setSortingEnabled(true); + + //drag and drop + setDragEnabled(true); + setAcceptDrops(true); + setDropIndicatorShown(true); + setDragDropMode(QAbstractItemView::DragDrop); +} + +void ArchiveView::startDrag(Qt::DropActions supportedActions) +{ + //only start the drag if it's over the filename column. this allows dragging selection in + //tree/detail view + if (currentIndex().column() != 0) { + return; + } + + kDebug() << "Singling out the current selection..."; + selectionModel()->setCurrentIndex(currentIndex(), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); + QTreeView::startDrag(supportedActions); +} + + +void ArchiveView::dragEnterEvent(QDragEnterEvent * event) +{ + //TODO: if no model, trigger some mechanism to create one automatically! + kDebug() << event; + + if (event->source() == this) { + //we don't support internal drops yet. + return; + } + + QTreeView::dragEnterEvent(event); +} + +void ArchiveView::dropEvent(QDropEvent * event) +{ + kDebug() << event; + + if (event->source() == this) { + //we don't support internal drops yet. + return; + } + + QTreeView::dropEvent(event); +} + +void ArchiveView::dragMoveEvent(QDragMoveEvent * event) +{ + if (event->source() == this) { + //we don't support internal drops yet. + return; + } + + QTreeView::dragMoveEvent(event); + if (event->mimeData()->hasFormat(QLatin1String("text/uri-list"))) { + event->acceptProposedAction(); + } +} diff --git a/ark/part/archiveview.h b/ark/part/archiveview.h new file mode 100644 index 00000000..daac59b1 --- /dev/null +++ b/ark/part/archiveview.h @@ -0,0 +1,54 @@ +/* + * ark -- archiver for the KDE project + * + * Copyright (C) 2008 Harald Hvaal + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef ARCHIVEVIEW_H +#define ARCHIVEVIEW_H + +#include + +class ArchiveView : public QTreeView +{ + Q_OBJECT + +public: + ArchiveView(QWidget *parent = 0); + virtual void dragEnterEvent(class QDragEnterEvent * event); + virtual void dropEvent(class QDropEvent * event); + virtual void dragMoveEvent(class QDragMoveEvent * event); + virtual void startDrag(Qt::DropActions supportedActions); + + void setModel(QAbstractItemModel *model); + +protected slots: + void slotClicked(const QModelIndex & index); + void slotDoubleClicked(const QModelIndex & index); + +private slots: + void updateMouseButtons(); + +signals: + void itemTriggered(const QModelIndex & index); + +private: + Qt::MouseButtons m_mouseButtons; // FIXME: workaround until QTBUG-1067 is resolved +}; + +#endif /* ARCHIVEVIEW_H */ diff --git a/ark/part/ark_part.desktop.cmake b/ark/part/ark_part.desktop.cmake new file mode 100644 index 00000000..84409bc0 --- /dev/null +++ b/ark/part/ark_part.desktop.cmake @@ -0,0 +1,152 @@ +[Desktop Entry] +Icon=ark +Name=Archiver +Name[af]=Argifeerder +Name[ar]=المؤرشف +Name[ast]=Archivador +Name[bg]=Архиватор +Name[br]=Dieller +Name[bs]=Arhivar +Name[ca]=Arxivador +Name[ca@valencia]=Arxivador +Name[cs]=Archivátor +Name[cy]=Archifydd +Name[da]=Arkivbehandler +Name[de]=Archivprogramm +Name[el]=Πρόγραμμα αρχειοθέτησης +Name[en_GB]=Archiver +Name[eo]=Arkivilo +Name[es]=Archivador +Name[et]=Arhiveerija +Name[eu]=Artxibatzeko programa +Name[fa]=بایگانی‌کننده +Name[fi]=Pakkausohjelma +Name[fr]=Archiveur +Name[ga]=Archiver +Name[gl]=Arquivador +Name[he]=מנהל הארכיונים +Name[hne]=अभिलेखक +Name[hr]=Arhiver +Name[hu]=Ark fájltömörítő +Name[ia]=Archivator +Name[id]=Pengarsip +Name[is]=Skráasafnari +Name[it]=Utilità di archiviazione +Name[ja]=アーカイバ +Name[kk]=Архивтегіш +Name[km]=កម្មវិធីប័ណ្ណសារ +Name[ko]=Archiver +Name[lt]=Archyvatorius +Name[lv]=Arhivātors +Name[mk]=Архивер +Name[mr]=संग्रहकर्ता +Name[ms]=Pengarkib +Name[nb]=Arkivbehandler +Name[nds]=Archivater +Name[ne]=सङ्ग्रहक +Name[nl]=Archiefgereedschap +Name[nn]=Arkivprogram +Name[pa]=ਅਕਾਇਵਰ +Name[pl]=Ark +Name[pt]=Ark +Name[pt_BR]=Arquivador +Name[ro]=Arhivator +Name[ru]=Архиватор +Name[sk]=Archivačný nástroj +Name[sl]=Pakirnik +Name[sq]=Arkivues +Name[sr]=Архивар +Name[sr@ijekavian]=Архивар +Name[sr@ijekavianlatin]=Arhivar +Name[sr@latin]=Arhivar +Name[sv]=Arkiverare +Name[ta]=காப்பகம் உருவாக்கி +Name[tg]=Бойгонигар +Name[th]=ตัวสร้างแฟ้มจัดเก็บ +Name[tr]=Arşivleyici +Name[uk]=Архіватор +Name[uz]=Arxivlagich +Name[uz@cyrillic]=Архивлагич +Name[vi]=Trình Lưu Trữ +Name[wa]=Årtchiveu +Name[xh]=Umenzi woshicilelo lukawonke-wonke noxwebhu lweMbali +Name[x-test]=xxArchiverxx +Name[zh_CN]=压缩存档工具 +Name[zh_TW]=壓縮檔處理器 +Comment=Archive Handling Tool +Comment[af]=Argief Handtering Program +Comment[ar]=أداة التعامل مع الملفات المضغوطة +Comment[ast]=Ferramienta pa remanar archivadores +Comment[bg]=Работа с архиви +Comment[br]=Ostilh merañ an dielloù +Comment[bs]=Alatka za rukovanje arhivama +Comment[ca]=Eina per a treballar amb arxius +Comment[ca@valencia]=Eina per a treballar amb arxius +Comment[cs]=Program pro práci s archivy +Comment[cy]=Erfyn Triniaeth Archif +Comment[da]=Arkivbehandlingsværktøj +Comment[de]=Archiv-Verwaltung +Comment[el]=Εργαλείο χειρισμού αρχειοθηκών +Comment[en_GB]=Archive Handling Tool +Comment[eo]=Administrilo por arĥivoj +Comment[es]=Herramienta de gestión de archivos +Comment[et]=Arhiivide haldamise rakendus +Comment[eu]=Artxiboak kudeatzeko tresna +Comment[fa]=ابزار گرداندن بایگانی +Comment[fi]=Pakkausohjelma +Comment[fr]=Outil de manipulation d'archives +Comment[ga]=Uirlis Láimhseála Cartlainne +Comment[gl]=Utilidade de manexo de arquivos +Comment[he]=כלי לניהול ארכיונים +Comment[hne]=अभिलेख संभाल औजार +Comment[hr]=Uslužni program za arhiviranje +Comment[hu]=Tömörítőprogram +Comment[ia]=Instrumento de manear archivo +Comment[id]=Perkakas Penanganan Arsip +Comment[is]=Vinna með safnskrár +Comment[it]=Gestione degli archivi +Comment[ja]=アーカイブを操作するツール +Comment[kk]=Архивпен айналысу құралы +Comment[km]=ឧបករណ៍​គ្រប់គ្រង​ប័ណ្ណសារ +Comment[ko]=압축 파일 도구 +Comment[lt]=Archyvo valdymo priemonė +Comment[lv]=Arhīvu apstrādes rīks +Comment[mk]=Алатка за справување со архивирани датотеки +Comment[mr]=संग्रह हाताळणारे साधन +Comment[ms]=Alatan Pengendalian Arkib +Comment[nb]=Arkivbehandlingsverktøy +Comment[nds]=En Warktüüch för de Archievpleeg +Comment[ne]=ह्यान्डलिङ उपकरण सङ्ग्रह गर्नुहोस् +Comment[nl]=Hulpprogramma voor het beheren van archieven +Comment[nn]=Verktøy for arkivhandsaming +Comment[pa]=ਅਕਾਇਵ ਹੈਡਲਿੰਗ ਟੂਲ +Comment[pl]=Program obsługi archiwów +Comment[pt]=Programa de gestão de arquivos +Comment[pt_BR]=Ferramenta de manipulação de arquivos +Comment[ro]=Utilitar de manipulare arhive +Comment[ru]=Программа работы с архивами +Comment[sk]=Nástroj na prácu s archívmi +Comment[sl]=Orodje za ravnanje z arhivi +Comment[sq]=Mjet Për Përpunimin e Arkivave +Comment[sr]=Алатка за руковање архивама +Comment[sr@ijekavian]=Алатка за руковање архивама +Comment[sr@ijekavianlatin]=Alatka za rukovanje arhivama +Comment[sr@latin]=Alatka za rukovanje arhivama +Comment[sv]=Verktyg för att hantera filarkiv +Comment[ta]=காப்பகத்தை கையாளும் கருவி +Comment[tg]=Асбобҳои Дасткории Бойгонӣ +Comment[th]=เครื่องมือจัดการแฟ้มจัดเก็บ +Comment[tr]=Arşiv İşleme Aracı +Comment[uk]=Засіб роботи з архівами +Comment[uz]=Arxiv uchun vosita +Comment[uz@cyrillic]=Архив учун восита +Comment[vi]=Công Cụ Xử Lý Các Tập Tin Nén +Comment[wa]=Usteye po-z apougnî les årtchives +Comment[xh]=Isixhobo sokuphatha i Archive +Comment[x-test]=xxArchive Handling Toolxx +Comment[zh_CN]=压缩归档处理工具 +Comment[zh_TW]=壓縮檔處理工具 +X-KDE-ServiceTypes=KParts/ReadOnlyPart,Browser/View +X-KDE-Library=arkpart +Type=Service +MimeType=@SUPPORTED_ARK_MIMETYPES@ diff --git a/ark/part/ark_part.rc b/ark/part/ark_part.rc new file mode 100644 index 00000000..044c11a5 --- /dev/null +++ b/ark/part/ark_part.rc @@ -0,0 +1,28 @@ + + + + + &File + + + + &Action + + + + + + + + &Settings + + + + + + + + + + + diff --git a/ark/part/arkviewer.cpp b/ark/part/arkviewer.cpp new file mode 100644 index 00000000..13a9f3c8 --- /dev/null +++ b/ark/part/arkviewer.cpp @@ -0,0 +1,266 @@ +/* + * ark: A program for modifying archives via a GUI. + * + * Copyright (C) 2004-2008 Henrique Pinto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "arkviewer.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +ArkViewer::ArkViewer(QWidget * parent, Qt::WFlags flags) + : KDialog(parent, flags) +{ + setButtons(Close); + m_widget = new KVBox(this); + m_widget->layout()->setSpacing(10); + + setMainWidget(m_widget); + + connect(this, SIGNAL(finished()), SLOT(dialogClosed())); +} + +ArkViewer::~ArkViewer() +{ +} + +void ArkViewer::dialogClosed() +{ + KConfigGroup conf = KGlobal::config()->group("Viewer"); + saveDialogSize(conf); + + if (m_part) { + KProgressDialog progressDialog + (this, i18n("Closing preview"), + i18n("Please wait while the preview is being closed...")); + + progressDialog.setMinimumDuration(500); + progressDialog.setModal(true); + progressDialog.setAllowCancel(false); + progressDialog.progressBar()->setRange(0, 0); + + // #261785: this preview dialog is not modal, so we need to delete + // the previewed file ourselves when the dialog is closed; + // we used to remove it at the end of ArkViewer::view() when + // QDialog::exec() was called instead of QDialog::show(). + const QString previewedFilePath(m_part.data()->url().pathOrUrl()); + + m_part.data()->closeUrl(); + + if (!previewedFilePath.isEmpty()) { + QFile::remove(previewedFilePath); + } + } +} + +void ArkViewer::view(const QString& fileName, QWidget *parent) +{ + KMimeType::Ptr mimeType = KMimeType::findByPath(fileName); + kDebug() << "MIME type" << mimeType->name(); + KService::Ptr viewer = ArkViewer::getViewer(mimeType); + + const bool needsExternalViewer = (!viewer.isNull() && + !viewer->hasServiceType(QLatin1String("KParts/ReadOnlyPart"))); + if (needsExternalViewer) { + // We have already resolved the MIME type and the service above. + // So there is no point in using KRun::runUrl() which would need + // to do the same again. + + const KUrl::List fileUrlList = KUrl(fileName); + // The last argument (tempFiles) set to true means that the temporary + // file will be removed when the viewer application exits. + KRun::run(*viewer, fileUrlList, parent, true); + return; + } + + bool viewInInternalViewer = true; + if (viewer.isNull()) { + // No internal viewer available for the file. Ask the user if it + // should be previewed as text/plain. + + int response; + if (!mimeType->isDefault()) { + // File has a defined MIME type, and not the default + // application/octet-stream. So it could be viewable as + // plain text, ask the user. + response = KMessageBox::warningContinueCancel(parent, + i18n("The internal viewer cannot preview this type of file(%1).Do you want to try to view it as plain text?", mimeType->name()), + i18nc("@title:window", "Cannot Preview File"), + KGuiItem(i18nc("@action:button", "Preview as Text"), KIcon(QLatin1String("text-plain"))), + KStandardGuiItem::cancel(), + QString(QLatin1String("PreviewAsText_%1")).arg(mimeType->name())); + } + else { + // No defined MIME type, or the default application/octet-stream. + // There is still a possibility that it could be viewable as plain + // text, so ask the user. Not the same as the message/question + // above, because the wording and default are different. + response = KMessageBox::warningContinueCancel(parent, + i18n("The internal viewer cannot preview this unknown type of file.Do you want to try to view it as plain text?"), + i18nc("@title:window", "Cannot Preview File"), + KGuiItem(i18nc("@action:button", "Preview as Text"), KIcon(QLatin1String("text-plain"))), + KStandardGuiItem::cancel(), + QString(), + KMessageBox::Dangerous); + } + + if (response == KMessageBox::Cancel) { + viewInInternalViewer = false; + } + else { // set for viewer later + mimeType = KMimeType::mimeType(QLatin1String("text/plain")); + } + } + + if (viewInInternalViewer) { + ArkViewer *internalViewer = new ArkViewer(parent, Qt::Window); + if (internalViewer->viewInInternalViewer(fileName, mimeType)) { + internalViewer->show(); + // The internal viewer is showing the file, and will + // remove the temporary file in dialogClosed(). So there + // is no more to do here. + return; + } + else { + KMessageBox::sorry(parent, i18n("The internal viewer cannot preview this file.")); + delete internalViewer; + } + } + + // Only get here if there is no internal viewer available or could be + // used for the file, and no external viewer was opened. Nothing can be + // done with the temporary file, so remove it now. + QFile::remove(fileName); +} + +void ArkViewer::keyPressEvent(QKeyEvent *event) +{ + KPushButton *defButton = button(defaultButton()); + + // Only handle the event the usual way if the default button has focus + // Otherwise, pressing enter on KatePart still closes the dialog, for example. + if ((defButton) && (defButton->hasFocus())) { + KDialog::keyPressEvent(event); + } + + event->accept(); +} + +// This sets the default size of the dialog. It will only take effect in the case +// where there is no saved size in the config file - it sets the default values +// for KDialog::restoreDialogSize(). +QSize ArkViewer::sizeHint() const +{ + return QSize(560, 400); +} + +bool ArkViewer::viewInInternalViewer(const QString& fileName, const KMimeType::Ptr& mimeType) +{ + const KUrl fileUrl(fileName); + + setCaption(fileUrl.fileName()); + restoreDialogSize(KGlobal::config()->group("Viewer")); + + QFrame *header = new QFrame(m_widget); + QHBoxLayout *headerLayout = new QHBoxLayout(header); + + QLabel *iconLabel = new QLabel(header); + headerLayout->addWidget(iconLabel); + iconLabel->setPixmap(KIconLoader::global()->loadMimeTypeIcon(mimeType->iconName(), KIconLoader::Desktop)); + iconLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum); + + KVBox *headerRight = new KVBox(header); + headerLayout->addWidget(headerRight); + new QLabel(QString(QLatin1String( "%1" )) + .arg(fileUrl.fileName()), headerRight + ); + new QLabel(mimeType->comment(), headerRight); + + header->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); + + m_part = KMimeTypeTrader::self()->createPartInstanceFromQuery(mimeType->name(), + m_widget, + this); + + if (!m_part.data()) { + return false; + } + + if (m_part.data()->browserExtension()) { + connect(m_part.data()->browserExtension(), + SIGNAL(openUrlRequestDelayed(KUrl,KParts::OpenUrlArguments,KParts::BrowserArguments)), + SLOT(slotOpenUrlRequestDelayed(KUrl,KParts::OpenUrlArguments,KParts::BrowserArguments))); + } + + m_part.data()->openUrl(fileUrl); + + return true; +} + +void ArkViewer::slotOpenUrlRequestDelayed(const KUrl& url, const KParts::OpenUrlArguments& arguments, const KParts::BrowserArguments& browserArguments) +{ + kDebug() << "Opening URL: " << url; + + Q_UNUSED(arguments) + Q_UNUSED(browserArguments) + + KRun *runner = new KRun(url, 0, 0, false); + runner->setRunExecutables(false); +} + +KService::Ptr ArkViewer::getViewer(const KMimeType::Ptr &mimeType) +{ + // No point in even trying to find anything for application/octet-stream + if (mimeType->isDefault()) { + return KService::Ptr(); + } + + // Try to get a read-only kpart for the internal viewer + KService::List offers = KMimeTypeTrader::self()->query(mimeType->name(), QString::fromLatin1("KParts/ReadOnlyPart")); + + // If we can't find a kpart, try to get an external application + if (offers.size() == 0) { + offers = KMimeTypeTrader::self()->query(mimeType->name(), QString::fromLatin1("Application")); + } + + if (offers.size() > 0) { + return offers.first(); + } else { + return KService::Ptr(); + } +} + + +#include "arkviewer.moc" diff --git a/ark/part/arkviewer.h b/ark/part/arkviewer.h new file mode 100644 index 00000000..bb41472e --- /dev/null +++ b/ark/part/arkviewer.h @@ -0,0 +1,63 @@ +/* + * ark: A program for modifying archives via a GUI. + * + * Copyright (C) 2004-2008, Henrique Pinto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef ARKVIEWER_H +#define ARKVIEWER_H + +#include +#include +#include +#include +#include + +#include + +class ArkViewer : public KDialog +{ + Q_OBJECT + +public: + virtual ~ArkViewer(); + virtual QSize sizeHint() const; + + static void view(const QString& fileName, QWidget* parent = 0); + +protected: + virtual void keyPressEvent(QKeyEvent *event); + +protected slots: + void slotOpenUrlRequestDelayed(const KUrl& url, const KParts::OpenUrlArguments& arguments, const KParts::BrowserArguments& browserArguments); + +private slots: + void dialogClosed(); + +private: + explicit ArkViewer(QWidget* parent = 0, Qt::WFlags flags = 0); + + static KService::Ptr getViewer(const KMimeType::Ptr& mimeType); + bool viewInInternalViewer(const QString& fileName, const KMimeType::Ptr& mimeType); + + QWeakPointer m_part; + QWidget *m_widget; +}; + +#endif // ARKVIEWER_H + diff --git a/ark/part/dnddbusinterface.xml b/ark/part/dnddbusinterface.xml new file mode 100644 index 00000000..66fb1fff --- /dev/null +++ b/ark/part/dnddbusinterface.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/ark/part/infopanel.cpp b/ark/part/infopanel.cpp new file mode 100644 index 00000000..433652ed --- /dev/null +++ b/ark/part/infopanel.cpp @@ -0,0 +1,213 @@ +/* + * ark -- archiver for the KDE project + * + * Copyright (C) 2007 Henrique Pinto + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ +#include "infopanel.h" +#include "kerfuffle/archive.h" + +#include +#include +#include + +#include +#include +#include +#include + +using namespace Kerfuffle; + +static QPixmap getMimeIcon(const QString& mimeName) +{ + return KIconLoader::global()->loadMimeTypeIcon(mimeName, KIconLoader::Desktop, KIconLoader::SizeHuge); +} + +InfoPanel::InfoPanel(ArchiveModel *model, QWidget *parent) + : QFrame(parent), m_model(model) +{ + setupUi(this); + + // Make the file name font bigger than the rest + QFont fnt = fileName->font(); + if (fnt.pointSize() > -1) { + fnt.setPointSize(fnt.pointSize() + 1); + } else { + fnt.setPixelSize(fnt.pixelSize() + 3); + } + fileName->setFont(fnt); + + updateWithDefaults(); +} + +InfoPanel::~InfoPanel() +{ +} + +void InfoPanel::updateWithDefaults() +{ + iconLabel->setPixmap(KIconLoader::global()->loadIcon(QLatin1String( "utilities-file-archiver" ), KIconLoader::Desktop, KIconLoader::SizeHuge)); + + const QString currentFileName = prettyFileName(); + + if (currentFileName.isEmpty()) { + fileName->setText(i18n("No archive loaded")); + } else { + fileName->setText(currentFileName); + } + + additionalInfo->setText(QString()); + hideMetaData(); + hideActions(); +} + +QString InfoPanel::prettyFileName() const +{ + if (m_prettyFileName.isEmpty()) { + if (m_model->archive()) { + QFileInfo fileInfo(m_model->archive()->fileName()); + return fileInfo.fileName(); + } + } + + return m_prettyFileName; +} + +void InfoPanel::setPrettyFileName(const QString& fileName) +{ + m_prettyFileName = fileName; +} + +void InfoPanel::setIndex(const QModelIndex& index) +{ + if (!index.isValid()) { + updateWithDefaults(); + } else { + const ArchiveEntry& entry = m_model->entryForIndex(index); + + KMimeType::Ptr mimeType; + + if (entry[ IsDirectory ].toBool()) { + mimeType = KMimeType::mimeType(QLatin1String( "inode/directory" )); + } else { + mimeType = KMimeType::findByPath(entry[ FileName ].toString(), 0, true); + } + + iconLabel->setPixmap(getMimeIcon(mimeType->iconName())); + if (entry[ IsDirectory ].toBool()) { + int dirs; + int files; + const int children = m_model->childCount(index, dirs, files); + additionalInfo->setText(KIO::itemsSummaryString(children, files, dirs, 0, false)); + } else if (entry.contains(Link)) { + additionalInfo->setText(i18n("Symbolic Link")); + } else { + if (entry.contains(Size)) { + additionalInfo->setText(KIO::convertSize(entry[ Size ].toULongLong())); + } else { + additionalInfo->setText(i18n("Unknown size")); + + } + } + + const QStringList nameParts = entry[ FileName ].toString().split(QLatin1Char( '/' ), QString::SkipEmptyParts); + const QString name = (nameParts.count() > 0) ? nameParts.last() : entry[ FileName ].toString(); + fileName->setText(name); + + metadataLabel->setText(metadataTextFor(index)); + showMetaData(); + } +} + +void InfoPanel::setIndexes(const QModelIndexList &list) +{ + if (list.size() == 0) { + setIndex(QModelIndex()); + } else if (list.size() == 1) { + setIndex(list[ 0 ]); + } else { + iconLabel->setPixmap(KIconLoader::global()->loadIcon(QLatin1String( "utilities-file-archiver" ), KIconLoader::Desktop, KIconLoader::SizeHuge)); + fileName->setText(i18np("One file selected", "%1 files selected", list.size())); + quint64 totalSize = 0; + foreach(const QModelIndex& index, list) { + const ArchiveEntry& entry = m_model->entryForIndex(index); + totalSize += entry[ Size ].toULongLong(); + } + additionalInfo->setText(KIO::convertSize(totalSize)); + hideMetaData(); + } +} + +void InfoPanel::showMetaData() +{ + firstSeparator->show(); + metadataLabel->show(); +} + +void InfoPanel::hideMetaData() +{ + firstSeparator->hide(); + metadataLabel->hide(); +} + +void InfoPanel::showActions() +{ + secondSeparator->show(); + actionsLabel->show(); +} + +void InfoPanel::hideActions() +{ + secondSeparator->hide(); + actionsLabel->hide(); +} + +QString InfoPanel::metadataTextFor(const QModelIndex &index) +{ + const ArchiveEntry& entry = m_model->entryForIndex(index); + QString text; + + KMimeType::Ptr mimeType; + + if (entry[ IsDirectory ].toBool()) { + mimeType = KMimeType::mimeType(QLatin1String( "inode/directory" )); + } else { + mimeType = KMimeType::findByPath(entry[ FileName ].toString(), 0, true); + } + + text += i18n("Type: %1
", mimeType->comment()); + + if (entry.contains(Owner)) { + text += i18n("Owner: %1
", entry[ Owner ].toString()); + } + + if (entry.contains(Group)) { + text += i18n("Group: %1
", entry[ Group ].toString()); + } + + if (entry.contains(Link)) { + text += i18n("Target: %1
", entry[ Link ].toString()); + } + + if (entry.contains(IsPasswordProtected) && entry[ IsPasswordProtected ].toBool()) { + text += i18n("Password protected: Yes
"); + } + + return text; +} + +#include "infopanel.moc" diff --git a/ark/part/infopanel.h b/ark/part/infopanel.h new file mode 100644 index 00000000..874d9b35 --- /dev/null +++ b/ark/part/infopanel.h @@ -0,0 +1,76 @@ +/* + * ark -- archiver for the KDE project + * + * Copyright (C) 2007 Henrique Pinto + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef INFOPANEL_H +#define INFOPANEL_H + +#include "kerfuffle/archive.h" +#include "archivemodel.h" +#include "ui_infopanel.h" + +#include + +class InfoPanel: public QFrame, Ui::InformationPanel +{ + Q_OBJECT +public: + explicit InfoPanel(ArchiveModel *model, QWidget *parent = 0); + virtual ~InfoPanel(); + + void setIndex(const QModelIndex &); + void setIndexes(const QModelIndexList &list); + + /** + * Returns the file name that is displayed on the info panel. + * + * @return The current file name. If no pretty name has been + * set, it returns the name of the loaded archive. + */ + QString prettyFileName() const; + + /** + * Sets a different file name for the current open archive. + * + * This is particularly useful when a temporary archive (from + * a remote location) is loaded, and the window title shows the + * remote file name and the info panel, by default, would show + * the name of the temporary downloaded file. + * + * @param fileName The new file name. + */ + void setPrettyFileName(const QString& fileName); + + void updateWithDefaults(); + +private: + void showMetaData(); + void hideMetaData(); + + void showActions(); + void hideActions(); + + QString metadataTextFor(const QModelIndex &); + + ArchiveModel *m_model; + QString m_prettyFileName; +}; + +#endif // INFOPANEL_H diff --git a/ark/part/infopanel.ui b/ark/part/infopanel.ui new file mode 100644 index 00000000..c6be12f1 --- /dev/null +++ b/ark/part/infopanel.ui @@ -0,0 +1,126 @@ + + + InformationPanel + + + + 0 + 0 + 118 + 300 + + + + + 0 + 0 + + + + Information Panel + + + + + + + + + Qt::AlignCenter + + + + + + + + 75 + true + + + + KSqueezedTextLabel + + + Qt::AlignCenter + + + true + + + Qt::ElideRight + + + + + + + Unknown file type + + + Qt::AlignCenter + + + + + + + Qt::Horizontal + + + + + + + Metadata Label + + + 10 + + + 20 + + + + + + + Qt::Horizontal + + + + + + + ActionsLabel + + + Qt::AlignCenter + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + KSqueezedTextLabel + QLabel +
ksqueezedtextlabel.h
+
+
+ + +
diff --git a/ark/part/interface.h b/ark/part/interface.h new file mode 100644 index 00000000..40f59028 --- /dev/null +++ b/ark/part/interface.h @@ -0,0 +1,37 @@ +/* + * ark -- archiver for the KDE project + * + * Copyright (C) 2007 Henrique Pinto + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ +#ifndef INTERFACE_H +#define INTERFACE_H + +#include +#include + +class Interface +{ +public: + virtual ~Interface() {} + + virtual bool isBusy() const = 0; +}; + +Q_DECLARE_INTERFACE(Interface, "org.kde.kerfuffle.partinterface/0.42") + +#endif // INTERFACE_H diff --git a/ark/part/jobtracker.cpp b/ark/part/jobtracker.cpp new file mode 100644 index 00000000..8ed0ec32 --- /dev/null +++ b/ark/part/jobtracker.cpp @@ -0,0 +1,107 @@ +/* + * ark -- archiver for the KDE project + * + * Copyright (C) 2007 Henrique Pinto + * Copyright (C) 2008 Harald Hvaal + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ +#include "jobtracker.h" + +#include + +JobTrackerWidget::JobTrackerWidget(QWidget *parent) + : QFrame(parent) +{ + setupUi(this); +} + +JobTracker::JobTracker(QWidget *parent) + : KAbstractWidgetJobTracker(parent) +{ + m_ui = new JobTrackerWidget(parent); + resetUi(); +} + +JobTracker::~JobTracker() +{ + foreach(KJob *job, m_jobs) { + job->kill(); + delete job; + } +} + +void JobTracker::description(KJob *job, const QString &title, const QPair< QString, QString > &f1, const QPair< QString, QString > &f2) +{ + Q_UNUSED(job) + Q_UNUSED(f1) + Q_UNUSED(f2) + m_ui->descriptionLabel->setText(QString(QLatin1String( "%1" )).arg(title)); + m_ui->descriptionLabel->show(); +} + +void JobTracker::infoMessage(KJob *job, const QString &plain, const QString &rich) +{ + Q_UNUSED(job) + Q_UNUSED(rich) + m_ui->informationLabel->setText(plain); + m_ui->informationLabel->show(); +} + +void JobTracker::warning(KJob *job, const QString &plain, const QString &rich) +{ + Q_UNUSED(job) + Q_UNUSED(rich) + m_ui->informationLabel->setText(plain); +} + +void JobTracker::registerJob(KJob *job) +{ + m_jobs << job; + KJobTrackerInterface::registerJob(job); + m_ui->show(); + m_ui->informationLabel->hide(); + m_ui->progressBar->show(); +} + +void JobTracker::percent(KJob *job, unsigned long percent) +{ + Q_UNUSED(job) + m_ui->progressBar->setMaximum(100); + m_ui->progressBar->setMinimum(0); + m_ui->progressBar->setValue(percent); +} + +void JobTracker::unregisterJob(KJob *job) +{ + m_jobs.remove(job); + KJobTrackerInterface::unregisterJob(job); + resetUi(); +} + +void JobTracker::resetUi() +{ + m_ui->hide(); + m_ui->descriptionLabel->hide(); + m_ui->informationLabel->hide(); + m_ui->progressBar->setMaximum(0); + m_ui->progressBar->setMinimum(0); +} + +QWidget* JobTracker::widget(KJob *) +{ + return m_ui; +} diff --git a/ark/part/jobtracker.h b/ark/part/jobtracker.h new file mode 100644 index 00000000..08aa77a1 --- /dev/null +++ b/ark/part/jobtracker.h @@ -0,0 +1,68 @@ +/* + * ark -- archiver for the KDE project + * + * Copyright (C) 2007 Henrique Pinto + * Copyright (C) 2008 Harald Hvaal + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ +#ifndef JOBTRACKER_H +#define JOBTRACKER_H + +#include +#include +#include "ui_jobtracker.h" + +class KJob; + +class JobTrackerWidget: public QFrame, public Ui::JobTrackerWidget +{ + Q_OBJECT + +public: + JobTrackerWidget(QWidget *parent = 0); +}; + +class JobTracker: public KAbstractWidgetJobTracker +{ + Q_OBJECT + +public: + JobTracker(QWidget *parent = 0); + ~JobTracker(); + + virtual QWidget *widget(KJob *); + +public slots: + virtual void registerJob(KJob *job); + virtual void unregisterJob(KJob *job); + +protected slots: + virtual void description(KJob *job, const QString &title, const QPair< QString, QString > &f1, const QPair< QString, QString > &f2); + virtual void infoMessage(KJob *job, const QString &plain, const QString &rich); + virtual void warning(KJob *job, const QString &plain, const QString &rich); + + virtual void percent(KJob *job, unsigned long percent); + +private slots: + void resetUi(); + +private: + JobTrackerWidget *m_ui; + QSet m_jobs; +}; + +#endif // JOBTRACKER_H diff --git a/ark/part/jobtracker.ui b/ark/part/jobtracker.ui new file mode 100644 index 00000000..2b227793 --- /dev/null +++ b/ark/part/jobtracker.ui @@ -0,0 +1,92 @@ + + JobTrackerWidget + + + + 0 + 0 + 409 + 16 + + + + Job Tracker + + + + 4 + + + 1 + + + 4 + + + 1 + + + + + + 0 + 0 + + + + + 50 + 0 + + + + <b>Job Description</b> + + + + + + + + 0 + 0 + + + + + 50 + 0 + + + + Some Information about the job + + + + + + + + 0 + 0 + + + + + 50 + 0 + + + + 100 + + + -1 + + + + + + + + diff --git a/ark/part/part.cpp b/ark/part/part.cpp new file mode 100644 index 00000000..bae48825 --- /dev/null +++ b/ark/part/part.cpp @@ -0,0 +1,916 @@ +/* + * ark -- archiver for the KDE project + * + * Copyright (C) 2007 Henrique Pinto + * Copyright (C) 2008-2009 Harald Hvaal + * Copyright (C) 2009-2012 Raphael Kubo da Costa + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "part.h" +#include "archivemodel.h" +#include "archiveview.h" +#include "arkviewer.h" +#include "dnddbusinterfaceadaptor.h" +#include "infopanel.h" +#include "jobtracker.h" +#include "kerfuffle/archive.h" +#include "kerfuffle/extractiondialog.h" +#include "kerfuffle/jobs.h" +#include "kerfuffle/settings.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace Kerfuffle; + +K_PLUGIN_FACTORY(Factory, registerPlugin();) +K_EXPORT_PLUGIN(Factory("ark")) + +namespace Ark +{ + +static quint32 s_instanceCounter = 1; + +Part::Part(QWidget *parentWidget, QObject *parent, const QVariantList& args) + : KParts::ReadWritePart(parent), + m_splitter(0), + m_busy(false), + m_jobTracker(0) +{ + Q_UNUSED(args) + setComponentData(Factory::componentData(), false); + + new DndExtractAdaptor(this); + + const QString pathName = QString(QLatin1String("/DndExtract/%1")).arg(s_instanceCounter++); + if (!QDBusConnection::sessionBus().registerObject(pathName, this)) { + kFatal() << "Could not register a D-Bus object for drag'n'drop"; + } + + m_model = new ArchiveModel(pathName, this); + + m_splitter = new QSplitter(Qt::Horizontal, parentWidget); + setWidget(m_splitter); + + m_view = new ArchiveView; + m_infoPanel = new InfoPanel(m_model); + + m_splitter->addWidget(m_view); + m_splitter->addWidget(m_infoPanel); + + QList splitterSizes = ArkSettings::splitterSizes(); + if (splitterSizes.isEmpty()) { + splitterSizes.append(200); + splitterSizes.append(100); + } + m_splitter->setSizes(splitterSizes); + + setupView(); + setupActions(); + + connect(m_model, SIGNAL(loadingStarted()), + this, SLOT(slotLoadingStarted())); + connect(m_model, SIGNAL(loadingFinished(KJob*)), + this, SLOT(slotLoadingFinished(KJob*))); + connect(m_model, SIGNAL(droppedFiles(QStringList,QString)), + this, SLOT(slotAddFiles(QStringList,QString))); + connect(m_model, SIGNAL(error(QString,QString)), + this, SLOT(slotError(QString,QString))); + + connect(this, SIGNAL(busy()), + this, SLOT(setBusyGui())); + connect(this, SIGNAL(ready()), + this, SLOT(setReadyGui())); + connect(this, SIGNAL(completed()), + this, SLOT(setFileNameFromArchive())); + + m_statusBarExtension = new KParts::StatusBarExtension(this); + + setXMLFile(QLatin1String( "ark_part.rc" )); +} + +Part::~Part() +{ + factory()->removeClient(this); + + saveSplitterSizes(); + + m_extractFilesAction->menu()->deleteLater(); +} + +void Part::registerJob(KJob* job) +{ + if (!m_jobTracker) { + m_jobTracker = new JobTracker(widget()); + m_statusBarExtension->addStatusBarItem(m_jobTracker->widget(0), 0, true); + m_jobTracker->widget(job)->show(); + } + m_jobTracker->registerJob(job); + + emit busy(); + connect(job, SIGNAL(result(KJob*)), this, SIGNAL(ready())); +} + +// TODO: One should construct a KUrl out of localPath in order to be able to handle +// non-local destinations (ie. trash:/ or a remote location) +// See bugs #189322 and #204323. +void Part::extractSelectedFilesTo(const QString& localPath) +{ + kDebug() << "Extract to " << localPath; + if (!m_model) { + return; + } + + if (m_view->selectionModel()->selectedRows().count() != 1) { + m_view->selectionModel()->setCurrentIndex(m_view->currentIndex(), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); + } + if (m_view->selectionModel()->selectedRows().count() != 1) { + return; + } + + QVariant internalRoot; + kDebug() << "valid " << m_view->currentIndex().parent().isValid(); + if (m_view->currentIndex().parent().isValid()) { + internalRoot = m_model->entryForIndex(m_view->currentIndex().parent()).value(InternalID); + } + + if (internalRoot.isNull()) { + //we have the special case valid parent, but the parent does not + //actually correspond to an item in the archive, but an automatically + //created folder. for now, we will just use the filename of the node + //instead, but for plugins that rely on a non-filename value as the + //InternalId, this WILL break things. TODO find a solution + internalRoot = m_model->entryForIndex(m_view->currentIndex().parent()).value(FileName); + } + + QList files = selectedFilesWithChildren(); + if (files.isEmpty()) { + return; + } + + kDebug() << "selected files are " << files; + Kerfuffle::ExtractionOptions options; + options[QLatin1String( "PreservePaths" )] = true; + if (!internalRoot.isNull()) { + options[QLatin1String("RootNode")] = internalRoot; + } + + ExtractJob *job = m_model->extractFiles(files, localPath, options); + registerJob(job); + + connect(job, SIGNAL(result(KJob*)), + this, SLOT(slotExtractionDone(KJob*))); + + job->start(); +} + +void Part::setupView() +{ + m_view->setModel(m_model); + + m_view->setSortingEnabled(true); + + connect(m_view->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), + this, SLOT(updateActions())); + connect(m_view->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), + this, SLOT(selectionChanged())); + + //TODO: fix an actual eventhandler + connect(m_view, SIGNAL(itemTriggered(QModelIndex)), + this, SLOT(slotPreview(QModelIndex))); + + connect(m_model, SIGNAL(columnsInserted(QModelIndex,int,int)), + this, SLOT(adjustColumns())); +} + +void Part::setupActions() +{ + KToggleAction *showInfoPanelAction = new KToggleAction(i18nc("@action:inmenu", "Show information panel"), this); + actionCollection()->addAction(QLatin1String( "show-infopanel" ), showInfoPanelAction); + showInfoPanelAction->setChecked(m_splitter->sizes().at(1) > 0); + connect(showInfoPanelAction, SIGNAL(triggered(bool)), + this, SLOT(slotToggleInfoPanel(bool))); + + m_saveAsAction = KStandardAction::saveAs(this, SLOT(slotSaveAs()), actionCollection()); + + m_previewAction = actionCollection()->addAction(QLatin1String( "preview" )); + m_previewAction->setText(i18nc("to preview a file inside an archive", "Pre&view")); + m_previewAction->setIcon(KIcon( QLatin1String( "document-preview-archive" ))); + m_previewAction->setStatusTip(i18n("Click to preview the selected file")); + m_previewAction->setShortcuts(QList() << Qt::Key_Return << Qt::Key_Space); + connect(m_previewAction, SIGNAL(triggered(bool)), + this, SLOT(slotPreview())); + + m_extractFilesAction = actionCollection()->addAction(QLatin1String( "extract" )); + m_extractFilesAction->setText(i18n("E&xtract")); + m_extractFilesAction->setIcon(KIcon( QLatin1String( "archive-extract" ))); + m_extractFilesAction->setStatusTip(i18n("Click to open an extraction dialog, where you can choose to extract either all files or just the selected ones")); + m_extractFilesAction->setShortcut(QKeySequence( QLatin1String( "Ctrl+E" ) )); + connect(m_extractFilesAction, SIGNAL(triggered(bool)), + this, SLOT(slotExtractFiles())); + + m_addFilesAction = actionCollection()->addAction(QLatin1String( "add" )); + m_addFilesAction->setIcon(KIcon( QLatin1String( "archive-insert" ))); + m_addFilesAction->setText(i18n("Add &File...")); + m_addFilesAction->setStatusTip(i18n("Click to add files to the archive")); + connect(m_addFilesAction, SIGNAL(triggered(bool)), + this, SLOT(slotAddFiles())); + + m_addDirAction = actionCollection()->addAction(QLatin1String( "add-dir" )); + m_addDirAction->setIcon(KIcon( QLatin1String( "archive-insert-directory" ))); + m_addDirAction->setText(i18n("Add Fo&lder...")); + m_addDirAction->setStatusTip(i18n("Click to add a folder to the archive")); + connect(m_addDirAction, SIGNAL(triggered(bool)), + this, SLOT(slotAddDir())); + + m_deleteFilesAction = actionCollection()->addAction(QLatin1String( "delete" )); + m_deleteFilesAction->setIcon(KIcon( QLatin1String( "archive-remove" ))); + m_deleteFilesAction->setText(i18n("De&lete")); + m_deleteFilesAction->setShortcut(Qt::Key_Delete); + m_deleteFilesAction->setStatusTip(i18n("Click to delete the selected files")); + connect(m_deleteFilesAction, SIGNAL(triggered(bool)), + this, SLOT(slotDeleteFiles())); + + updateActions(); +} + +void Part::updateActions() +{ + bool isWritable = m_model->archive() && (!m_model->archive()->isReadOnly()); + + m_previewAction->setEnabled(!isBusy() && (m_view->selectionModel()->selectedRows().count() == 1) + && isPreviewable(m_view->selectionModel()->currentIndex())); + m_extractFilesAction->setEnabled(!isBusy() && (m_model->rowCount() > 0)); + m_addFilesAction->setEnabled(!isBusy() && isWritable); + m_addDirAction->setEnabled(!isBusy() && isWritable); + m_deleteFilesAction->setEnabled(!isBusy() && (m_view->selectionModel()->selectedRows().count() > 0) + && isWritable); + + QMenu *menu = m_extractFilesAction->menu(); + if (!menu) { + menu = new QMenu; + m_extractFilesAction->setMenu(menu); + connect(menu, SIGNAL(triggered(QAction*)), + this, SLOT(slotQuickExtractFiles(QAction*))); + + // Remember to keep this action's properties as similar to + // m_extractFilesAction's as possible (except where it does not make + // sense, such as the text or the shortcut). + QAction *extractTo = menu->addAction(i18n("Extract To...")); + extractTo->setIcon(m_extractFilesAction->icon()); + extractTo->setStatusTip(m_extractFilesAction->statusTip()); + connect(extractTo, SIGNAL(triggered(bool)), SLOT(slotExtractFiles())); + + menu->addSeparator(); + + QAction *header = menu->addAction(i18n("Quick Extract To...")); + header->setEnabled(false); + header->setIcon(KIcon( QLatin1String( "archive-extract" ))); + } + + while (menu->actions().size() > 3) { + menu->removeAction(menu->actions().last()); + } + + const KConfigGroup conf(KGlobal::config(), "DirSelect Dialog"); + const QStringList dirHistory = conf.readPathEntry("History Items", QStringList()); + + for (int i = 0; i < qMin(10, dirHistory.size()); ++i) { + const KUrl dirUrl(dirHistory.at(i)); + QAction *newAction = menu->addAction(dirUrl.pathOrUrl()); + newAction->setData(dirUrl.pathOrUrl()); + } +} + +void Part::slotQuickExtractFiles(QAction *triggeredAction) +{ + // #190507: triggeredAction->data.isNull() means it's the "Extract to..." + // action, and we do not want it to run here + if (!triggeredAction->data().isNull()) { + kDebug() << "Extract to " << triggeredAction->data().toString(); + + const QString userDestination = triggeredAction->data().toString(); + QString finalDestinationDirectory; + const QString detectedSubfolder = detectSubfolder(); + + if (!isSingleFolderArchive()) { + finalDestinationDirectory = userDestination + + QDir::separator() + detectedSubfolder; + QDir(userDestination).mkdir(detectedSubfolder); + } else { + finalDestinationDirectory = userDestination; + } + + Kerfuffle::ExtractionOptions options; + options[QLatin1String( "PreservePaths" )] = true; + QList files = selectedFiles(); + ExtractJob *job = m_model->extractFiles(files, finalDestinationDirectory, options); + registerJob(job); + + connect(job, SIGNAL(result(KJob*)), + this, SLOT(slotExtractionDone(KJob*))); + + job->start(); + } +} + +bool Part::isPreviewable(const QModelIndex& index) const +{ + return index.isValid() && (!m_model->entryForIndex(index)[ IsDirectory ].toBool()); +} + +void Part::selectionChanged() +{ + m_infoPanel->setIndexes(m_view->selectionModel()->selectedRows()); +} + +KAboutData* Part::createAboutData() +{ + return new KAboutData("ark", 0, ki18n("ArkPart"), "3.0"); +} + +bool Part::openFile() +{ + const QString localFile(localFilePath()); + const QFileInfo localFileInfo(localFile); + const bool creatingNewArchive = + arguments().metaData()[QLatin1String("createNewArchive")] == QLatin1String("true"); + + if (localFileInfo.isDir()) { + KMessageBox::error(NULL, i18nc("@info", + "%1 is a directory.", + localFile)); + return false; + } + + if (creatingNewArchive) { + if (localFileInfo.exists()) { + int overwrite = KMessageBox::questionYesNo(NULL, i18nc("@info", "The archive %1 already exists. Would you like to open it instead?", localFile), i18nc("@title:window", "File Exists"), KGuiItem(i18n("Open File")), KStandardGuiItem::cancel()); + + if (overwrite == KMessageBox::No) { + return false; + } + } + } else { + if (!localFileInfo.exists()) { + KMessageBox::sorry(NULL, i18nc("@info", "The archive %1 was not found.", localFile), i18nc("@title:window", "Error Opening Archive")); + return false; + } + } + + QScopedPointer archive(Kerfuffle::Archive::create(localFile, m_model)); + + if ((!archive) || ((creatingNewArchive) && (archive->isReadOnly()))) { + QStringList mimeTypeList; + QHash mimeTypes; + + if (creatingNewArchive) { + mimeTypeList = Kerfuffle::supportedWriteMimeTypes(); + } else { + mimeTypeList = Kerfuffle::supportedMimeTypes(); + } + + foreach(const QString& mime, mimeTypeList) { + KMimeType::Ptr mimePtr(KMimeType::mimeType(mime)); + if (mimePtr) { + // Key = "application/zip", Value = "Zip Archive" + mimeTypes[mime] = mimePtr->comment(); + } + } + + QStringList mimeComments(mimeTypes.values()); + mimeComments.sort(); + + bool ok; + QString item; + + if (creatingNewArchive) { + item = KInputDialog::getItem(i18nc("@title:window", "Invalid Archive Type"), + i18nc("@info", "Ark cannot create archives of the type you have chosen.Please choose another archive type below."), + mimeComments, 0, false, &ok); + } else { + item = KInputDialog::getItem(i18nc("@title:window", "Unable to Determine Archive Type"), + i18nc("@info", "Ark was unable to determine the archive type of the filename.Please choose the correct archive type below."), + mimeComments, + 0, + false, + &ok); + } + + if ((!ok) || (item.isEmpty())) { + return false; + } + + archive.reset(Kerfuffle::Archive::create(localFile, mimeTypes.key(item), m_model)); + } + + if (!archive) { + KMessageBox::sorry(NULL, i18nc("@info", "Ark was not able to open the archive %1. No plugin capable of handling the file was found.", localFile), i18nc("@title:window", "Error Opening Archive")); + return false; + } + + KJob *job = m_model->setArchive(archive.take()); + registerJob(job); + job->start(); + m_infoPanel->setIndex(QModelIndex()); + + if (arguments().metaData()[QLatin1String( "showExtractDialog" )] == QLatin1String( "true" )) { + QTimer::singleShot(0, this, SLOT(slotExtractFiles())); + } + + return true; +} + +bool Part::saveFile() +{ + return true; +} + +bool Part::isBusy() const +{ + return m_busy; +} + +void Part::slotLoadingStarted() +{ +} + +void Part::slotLoadingFinished(KJob *job) +{ + kDebug(); + + if (job->error()) { + if (arguments().metaData()[QLatin1String( "createNewArchive" )] != QLatin1String( "true" )) { + KMessageBox::sorry(NULL, i18nc("@info", "Loading the archive %1 failed with the following error: %2", localFilePath(), job->errorText()), i18nc("@title:window", "Error Opening Archive")); + + // The file failed to open, so reset the open archive, info panel and caption. + m_model->setArchive(NULL); + + m_infoPanel->setPrettyFileName(QString()); + m_infoPanel->updateWithDefaults(); + + emit setWindowCaption(QString()); + } + } + + m_view->sortByColumn(0, Qt::AscendingOrder); + m_view->expandToDepth(0); + + // After loading all files, resize the columns to fit all fields + m_view->header()->resizeSections(QHeaderView::ResizeToContents); + + updateActions(); +} + +void Part::setReadyGui() +{ + kDebug(); + QApplication::restoreOverrideCursor(); + m_busy = false; + m_view->setEnabled(true); + updateActions(); +} + +void Part::setBusyGui() +{ + kDebug(); + QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); + m_busy = true; + m_view->setEnabled(false); + updateActions(); +} + +void Part::setFileNameFromArchive() +{ + const QString prettyName = url().fileName(); + + m_infoPanel->setPrettyFileName(prettyName); + m_infoPanel->updateWithDefaults(); + + emit setWindowCaption(prettyName); +} + +void Part::slotPreview() +{ + slotPreview(m_view->selectionModel()->currentIndex()); +} + +void Part::slotPreview(const QModelIndex & index) +{ + if (!isPreviewable(index)) { + return; + } + + const ArchiveEntry& entry = m_model->entryForIndex(index); + + if (!entry.isEmpty()) { + Kerfuffle::ExtractionOptions options; + options[QLatin1String( "PreservePaths" )] = true; + + ExtractJob *job = m_model->extractFile(entry[ InternalID ], m_previewDir.name(), options); + registerJob(job); + connect(job, SIGNAL(result(KJob*)), + this, SLOT(slotPreviewExtracted(KJob*))); + job->start(); + } +} + +void Part::slotPreviewExtracted(KJob *job) +{ + // FIXME: the error checking here isn't really working + // if there's an error or an overwrite dialog, + // the preview dialog will be launched anyway + if (!job->error()) { + const ArchiveEntry& entry = + m_model->entryForIndex(m_view->selectionModel()->currentIndex()); + + QString fullName = + m_previewDir.name() + QLatin1Char('/') + entry[FileName].toString(); + + // Make sure a maliciously crafted archive with parent folders named ".." do + // not cause the previewed file path to be located outside the temporary + // directory, resulting in a directory traversal issue. + fullName.remove(QLatin1String("../")); + + ArkViewer::view(fullName, widget()); + } else { + KMessageBox::error(widget(), job->errorString()); + } + setReadyGui(); +} + +void Part::slotError(const QString& errorMessage, const QString& details) +{ + if (details.isEmpty()) { + KMessageBox::error(widget(), errorMessage); + } else { + KMessageBox::detailedError(widget(), errorMessage, details); + } +} + +bool Part::isSingleFolderArchive() const +{ + return m_model->archive()->isSingleFolderArchive(); +} + +QString Part::detectSubfolder() const +{ + if (!m_model) { + return QString(); + } + + return m_model->archive()->subfolderName(); +} + +void Part::slotExtractFiles() +{ + if (!m_model) { + return; + } + + QWeakPointer dialog = new Kerfuffle::ExtractionDialog; + + if (m_view->selectionModel()->selectedRows().count() > 0) { + dialog.data()->setShowSelectedFiles(true); + } + + dialog.data()->setSingleFolderArchive(isSingleFolderArchive()); + dialog.data()->setSubfolder(detectSubfolder()); + + dialog.data()->setCurrentUrl(QFileInfo(m_model->archive()->fileName()).path()); + + if (dialog.data()->exec()) { + //this is done to update the quick extract menu + updateActions(); + + QVariantList files; + + //if the user has chosen to extract only selected entries, fetch these + //from the listview + if (!dialog.data()->extractAllFiles()) { + files = selectedFilesWithChildren(); + } + + kDebug() << "Selected " << files; + + Kerfuffle::ExtractionOptions options; + + if (dialog.data()->preservePaths()) { + options[QLatin1String("PreservePaths")] = true; + } + + options[QLatin1String("FollowExtractionDialogSettings")] = true; + + const QString destinationDirectory = dialog.data()->destinationDirectory().pathOrUrl(); + ExtractJob *job = m_model->extractFiles(files, destinationDirectory, options); + registerJob(job); + + connect(job, SIGNAL(result(KJob*)), + this, SLOT(slotExtractionDone(KJob*))); + + job->start(); + } + + delete dialog.data(); +} + +QList Part::selectedFilesWithChildren() +{ + Q_ASSERT(m_model); + + QModelIndexList toIterate = m_view->selectionModel()->selectedRows(); + + for (int i = 0; i < toIterate.size(); ++i) { + QModelIndex index = toIterate.at(i); + + for (int j = 0; j < m_model->rowCount(index); ++j) { + QModelIndex child = m_model->index(j, 0, index); + if (!toIterate.contains(child)) { + toIterate << child; + } + } + } + + QVariantList ret; + foreach(const QModelIndex & index, toIterate) { + const ArchiveEntry& entry = m_model->entryForIndex(index); + if (entry.contains(InternalID)) { + ret << entry[ InternalID ]; + } + } + return ret; +} + +QList Part::selectedFiles() +{ + QStringList toSort; + + foreach(const QModelIndex & index, m_view->selectionModel()->selectedRows()) { + const ArchiveEntry& entry = m_model->entryForIndex(index); + toSort << entry[ InternalID ].toString(); + } + + toSort.sort(); + QVariantList ret; + foreach(const QString &i, toSort) { + ret << i; + } + return ret; +} + +void Part::slotExtractionDone(KJob* job) +{ + kDebug(); + if (job->error()) { + KMessageBox::error(widget(), job->errorString()); + } else { + ExtractJob *extractJob = qobject_cast(job); + Q_ASSERT(extractJob); + + const bool followExtractionDialogSettings = + extractJob->extractionOptions().value(QLatin1String("FollowExtractionDialogSettings"), false).toBool(); + if (!followExtractionDialogSettings) { + return; + } + + if (ArkSettings::openDestinationFolderAfterExtraction()) { + + KUrl destinationDirectory(extractJob->destinationDirectory()); + destinationDirectory.cleanPath(); + + KRun::runUrl(destinationDirectory, QLatin1String("inode/directory"), widget()); + } + + if (ArkSettings::closeAfterExtraction()) { + emit quit(); + } + } +} + +void Part::adjustColumns() +{ + kDebug(); + + m_view->header()->setResizeMode(0, QHeaderView::ResizeToContents); +} + +void Part::slotAddFiles(const QStringList& filesToAdd, const QString& path) +{ + if (filesToAdd.isEmpty()) { + return; + } + + kDebug() << "Adding " << filesToAdd << " to " << path; + kDebug() << "Warning, for now the path argument is not implemented"; + + QStringList cleanFilesToAdd(filesToAdd); + for (int i = 0; i < cleanFilesToAdd.size(); ++i) { + QString& file = cleanFilesToAdd[i]; + if (QFileInfo(file).isDir()) { + if (!file.endsWith(QLatin1Char( '/' ))) { + file += QLatin1Char( '/' ); + } + } + } + + CompressionOptions options; + + QString firstPath = cleanFilesToAdd.first(); + if (firstPath.right(1) == QLatin1String( "/" )) { + firstPath.chop(1); + } + firstPath = QFileInfo(firstPath).dir().absolutePath(); + + kDebug() << "Detected relative path to be " << firstPath; + options[QLatin1String( "GlobalWorkDir" )] = firstPath; + + AddJob *job = m_model->addFiles(cleanFilesToAdd, options); + if (!job) { + return; + } + + connect(job, SIGNAL(result(KJob*)), + this, SLOT(slotAddFilesDone(KJob*))); + registerJob(job); + job->start(); +} + +void Part::slotAddFiles() +{ + kDebug(); + + // #264819: passing widget() as the parent will not work as expected. + // KFileDialog will create a KFileWidget, which runs an internal + // event loop to stat the given directory. This, in turn, leads to + // events being delivered to widget(), which is a QSplitter, which + // in turn reimplements childEvent() and will end up calling + // QWidget::show() on the KFileDialog (thus showing it in a + // non-modal state). + // When KFileDialog::exec() is called, the widget is already shown + // and nothing happens. + const QStringList filesToAdd = + KFileDialog::getOpenFileNames(KUrl("kfiledialog:///ArkAddFiles"), + QString(), widget()->parentWidget(), + i18nc("@title:window", "Add Files")); + + slotAddFiles(filesToAdd); +} + +void Part::slotAddDir() +{ + kDebug(); + const QString dirToAdd = KFileDialog::getExistingDirectory(KUrl("kfiledialog:///ArkAddFiles"), widget(), i18nc("@title:window", "Add Folder")); + + if (!dirToAdd.isEmpty()) { + slotAddFiles(QStringList() << dirToAdd); + } +} + +void Part::slotAddFilesDone(KJob* job) +{ + kDebug(); + if (job->error()) { + KMessageBox::error(widget(), job->errorString()); + } +} + +void Part::slotDeleteFilesDone(KJob* job) +{ + kDebug(); + if (job->error()) { + KMessageBox::error(widget(), job->errorString()); + } +} + +void Part::slotDeleteFiles() +{ + kDebug(); + + const int reallyDelete = + KMessageBox::questionYesNo(NULL, + i18n("Deleting these files is not undoable. Are you sure you want to do this?"), + i18nc("@title:window", "Delete files"), + KStandardGuiItem::del(), + KStandardGuiItem::cancel(), + QString(), + KMessageBox::Dangerous | KMessageBox::Notify); + + if (reallyDelete == KMessageBox::No) { + return; + } + + DeleteJob *job = m_model->deleteFiles(selectedFilesWithChildren()); + connect(job, SIGNAL(result(KJob*)), + this, SLOT(slotDeleteFilesDone(KJob*))); + registerJob(job); + job->start(); +} + +void Part::slotToggleInfoPanel(bool visible) +{ + QList splitterSizes; + + if (visible) { + splitterSizes = ArkSettings::splitterSizesWithBothWidgets(); + } else { + splitterSizes = m_splitter->sizes(); + ArkSettings::setSplitterSizesWithBothWidgets(splitterSizes); + splitterSizes[1] = 0; + } + + m_splitter->setSizes(splitterSizes); + saveSplitterSizes(); +} + +void Part::saveSplitterSizes() +{ + ArkSettings::setSplitterSizes(m_splitter->sizes()); + ArkSettings::self()->writeConfig(); +} + +void Part::slotSaveAs() +{ + KUrl saveUrl = KFileDialog::getSaveUrl(KUrl(QLatin1String( "kfiledialog:///ArkSaveAs/" ) + url().fileName()), QString(), widget()); + + if ((saveUrl.isValid()) && (!saveUrl.isEmpty())) { + if (KIO::NetAccess::exists(saveUrl, KIO::NetAccess::DestinationSide, widget())) { + int overwrite = KMessageBox::warningContinueCancel(widget(), + i18nc("@info", "An archive named %1 already exists. Are you sure you want to overwrite it?", saveUrl.fileName()), + QString(), + KStandardGuiItem::overwrite()); + + if (overwrite != KMessageBox::Continue) { + return; + } + } + + KUrl srcUrl = KUrl::fromPath(localFilePath()); + + if (!QFile::exists(localFilePath())) { + if (url().isLocalFile()) { + KMessageBox::error(widget(), + i18nc("@info", "The archive %1 cannot be copied to the specified location. The archive does not exist anymore.", localFilePath())); + + return; + } else { + srcUrl = url(); + } + } + + KIO::Job *copyJob = KIO::file_copy(srcUrl, saveUrl, -1, KIO::Overwrite); + + if (!KIO::NetAccess::synchronousRun(copyJob, widget())) { + KMessageBox::error(widget(), + i18nc("@info", "The archive could not be saved as %1. Try saving it to another location.", saveUrl.pathOrUrl())); + } + } +} + +} // namespace Ark diff --git a/ark/part/part.h b/ark/part/part.h new file mode 100644 index 00000000..5379b9fc --- /dev/null +++ b/ark/part/part.h @@ -0,0 +1,126 @@ +/* + * ark -- archiver for the KDE project + * + * Copyright (C) 2007 Henrique Pinto + * Copyright (C) 2008-2009 Harald Hvaal + * Copyright (C) 2009 Raphael Kubo da Costa + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ +#ifndef PART_H +#define PART_H + +#include "interface.h" + +#include +#include +#include + +#include + +class ArchiveModel; +class InfoPanel; + +class KAbstractWidgetJobTracker; +class KAboutData; +class KAction; +class KJob; + +class QAction; +class QSplitter; +class QTreeView; + +namespace Ark +{ + +class Part: public KParts::ReadWritePart, public Interface +{ + Q_OBJECT + Q_INTERFACES(Interface) +public: + Part(QWidget *parentWidget, QObject *parent, const QVariantList &); + ~Part(); + static KAboutData* createAboutData(); + + virtual bool openFile(); + virtual bool saveFile(); + + bool isBusy() const; + +public slots: + void extractSelectedFilesTo(const QString& localPath); + +private slots: + void slotLoadingStarted(); + void slotLoadingFinished(KJob *job); + void slotPreview(); + void slotPreview(const QModelIndex & index); + void slotPreviewExtracted(KJob*); + void slotError(const QString& errorMessage, const QString& details); + void slotExtractFiles(); + void slotExtractionDone(KJob*); + void slotQuickExtractFiles(QAction*); + void slotAddFiles(); + void slotAddFiles(const QStringList& files, const QString& path = QString()); + void slotAddDir(); + void slotAddFilesDone(KJob*); + void slotDeleteFiles(); + void slotDeleteFilesDone(KJob*); + void saveSplitterSizes(); + void slotToggleInfoPanel(bool); + void slotSaveAs(); + void updateActions(); + void selectionChanged(); + void adjustColumns(); + void setBusyGui(); + void setReadyGui(); + void setFileNameFromArchive(); + +signals: + void busy(); + void ready(); + void quit(); + +private: + void setupView(); + void setupActions(); + bool isSingleFolderArchive() const; + QString detectSubfolder() const; + bool isPreviewable(const QModelIndex& index) const; + QList selectedFiles(); + QList selectedFilesWithChildren(); + void registerJob(KJob *job); + + ArchiveModel *m_model; + QTreeView *m_view; + KAction *m_previewAction; + KAction *m_extractFilesAction; + KAction *m_addFilesAction; + KAction *m_addDirAction; + KAction *m_deleteFilesAction; + KAction *m_saveAsAction; + InfoPanel *m_infoPanel; + QSplitter *m_splitter; + KTempDir m_previewDir; + bool m_busy; + + KAbstractWidgetJobTracker *m_jobTracker; + KParts::StatusBarExtension *m_statusBarExtension; +}; + +} // namespace Ark + +#endif // PART_H diff --git a/ark/plugins/CLI-README b/ark/plugins/CLI-README new file mode 100644 index 00000000..0c21d49e --- /dev/null +++ b/ark/plugins/CLI-README @@ -0,0 +1,21 @@ +In this folder is a general template of what one needs to implement +support for a plugin using the cli interface in ark. + +Here are the steps. + +1. First, create a copy of the cliplugin folder +2. Change plugins/CMakeLists.txt to include the new subfolder +3. Rename the kerfuffle_cli.desktop to a unique name, for example +kerfuffle_rar.desktop. +4. Fill in the parts in the desktop marked with TODO +5. Update the plugins/yourplugin/CMakeLists.txt file, replacing all +instances of kerfuffle_cli with kerfuffle_yourplugin (where yourplugin + must be a unique plugin name) +6. Implement/modify cliplugin.cpp to fit your archive type. +Refer to kerfuffle/cliinterface.h for explanations on the values that +needs to be implemented. The class name does not need to be changed + +Then finally, email the plugin to the ark maintainer for a code review before +it is committed to trunk :D + +Have fun diff --git a/ark/plugins/CMakeLists.txt b/ark/plugins/CMakeLists.txt new file mode 100644 index 00000000..36194128 --- /dev/null +++ b/ark/plugins/CMakeLists.txt @@ -0,0 +1,28 @@ +if (LIBARCHIVE_FOUND) + if( HAVE_LIBARCHIVE_READ_DISK_API ) + if( NOT HAVE_LIBARCHIVE_LZMA_SUPPORT OR NOT HAVE_LIBARCHIVE_XZ_SUPPORT ) + message(STATUS "Your libarchive does not have support for lzma and/or xz archives. libarchive >= 2.7.0 is recommended.") + endif( NOT HAVE_LIBARCHIVE_LZMA_SUPPORT OR NOT HAVE_LIBARCHIVE_XZ_SUPPORT ) + if( NOT HAVE_LIBARCHIVE_RPM_SUPPORT ) + message(STATUS "Your libarchive does not have support for rpm archives. libarchive >= 2.8.0 is required for this.") + endif( NOT HAVE_LIBARCHIVE_RPM_SUPPORT ) + if( NOT HAVE_LIBARCHIVE_CAB_SUPPORT ) + message(STATUS "Your libarchive does not have support for cab archives. libarchive >= 3.0.0 is required for this.") + endif( NOT HAVE_LIBARCHIVE_CAB_SUPPORT ) + add_subdirectory( libarchive ) + else( HAVE_LIBARCHIVE_READ_DISK_API ) + # Remove the cached variables from FindLibArchive.cmake + unset( LIBARCHIVE_FOUND ) + unset( LIBARCHIVE_INCLUDE_DIR ) + unset( LIBARCHIVE_LIBRARY ) + message(STATUS "Your libarchive does not have support for archive_read_disk api. libarchive >= 2.6.0 is needed.") + endif( HAVE_LIBARCHIVE_READ_DISK_API ) +endif (LIBARCHIVE_FOUND) + +add_subdirectory( clirarplugin ) +add_subdirectory( cli7zplugin ) +add_subdirectory( clizipplugin ) +add_subdirectory( libsinglefileplugin ) +add_subdirectory( clilhaplugin ) + +set(SUPPORTED_ARK_MIMETYPES "${SUPPORTED_ARK_MIMETYPES}" PARENT_SCOPE) diff --git a/ark/plugins/cli7zplugin/CMakeLists.txt b/ark/plugins/cli7zplugin/CMakeLists.txt new file mode 100644 index 00000000..da56df20 --- /dev/null +++ b/ark/plugins/cli7zplugin/CMakeLists.txt @@ -0,0 +1,21 @@ +########### next target ############### + +set(SUPPORTED_CLI7Z_MIMETYPES "application/x-7z-compressed;") + +set(kerfuffle_cli7z_SRCS cliplugin.cpp) + +kde4_add_plugin(kerfuffle_cli7z ${kerfuffle_cli7z_SRCS}) + +target_link_libraries(kerfuffle_cli7z ${KDE4_KDECORE_LIBS} ${KDE4_KIO_LIBS} kerfuffle ) + +configure_file( + ${CMAKE_CURRENT_SOURCE_DIR}/kerfuffle_cli7z.desktop.cmake + ${CMAKE_CURRENT_BINARY_DIR}/kerfuffle_cli7z.desktop +) + +########### install files ############### + +install(TARGETS kerfuffle_cli7z DESTINATION ${PLUGIN_INSTALL_DIR} ) +install( FILES ${CMAKE_CURRENT_BINARY_DIR}/kerfuffle_cli7z.desktop DESTINATION ${SERVICES_INSTALL_DIR} ) + +set(SUPPORTED_ARK_MIMETYPES "${SUPPORTED_ARK_MIMETYPES}${SUPPORTED_CLI7Z_MIMETYPES}" PARENT_SCOPE) diff --git a/ark/plugins/cli7zplugin/cliplugin.cpp b/ark/plugins/cli7zplugin/cliplugin.cpp new file mode 100644 index 00000000..500f121d --- /dev/null +++ b/ark/plugins/cli7zplugin/cliplugin.cpp @@ -0,0 +1,180 @@ +/* + * ark -- archiver for the KDE project + * + * Copyright (C) 2009 Harald Hvaal + * Copyright (C) 2009-2011 Raphael Kubo da Costa + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "cliplugin.h" +#include "kerfuffle/cliinterface.h" +#include "kerfuffle/kerfuffle_export.h" + +#include +#include +#include +#include + +#include + +using namespace Kerfuffle; + +CliPlugin::CliPlugin(QObject *parent, const QVariantList & args) + : CliInterface(parent, args) + , m_archiveType(ArchiveType7z) + , m_state(ReadStateHeader) +{ +} + +CliPlugin::~CliPlugin() +{ +} + +ParameterList CliPlugin::parameterList() const +{ + static ParameterList p; + + if (p.isEmpty()) { + //p[CaptureProgress] = true; + p[ListProgram] = p[ExtractProgram] = p[DeleteProgram] = p[AddProgram] = QStringList() << QLatin1String( "7z" ) << QLatin1String( "7za" ) << QLatin1String( "7zr" ); + + p[ListArgs] = QStringList() << QLatin1String( "l" ) << QLatin1String( "-slt" ) << QLatin1String( "$Archive" ); + p[ExtractArgs] = QStringList() << QLatin1String( "$PreservePathSwitch" ) << QLatin1String( "$PasswordSwitch" ) << QLatin1String( "$Archive" ) << QLatin1String( "$Files" ); + p[PreservePathSwitch] = QStringList() << QLatin1String( "x" ) << QLatin1String( "e" ); + p[PasswordSwitch] = QStringList() << QLatin1String( "-p$Password" ); + p[FileExistsExpression] = QLatin1String( "already exists. Overwrite with" ); + p[WrongPasswordPatterns] = QStringList() << QLatin1String( "Wrong password" ); + p[AddArgs] = QStringList() << QLatin1String( "a" ) << QLatin1String( "$Archive" ) << QLatin1String( "$Files" ); + p[DeleteArgs] = QStringList() << QLatin1String( "d" ) << QLatin1String( "$Archive" ) << QLatin1String( "$Files" ); + + p[FileExistsInput] = QStringList() + << QLatin1String( "Y" ) //overwrite + << QLatin1String( "N" )//skip + << QLatin1String( "A" ) //overwrite all + << QLatin1String( "S" ) //autoskip + << QLatin1String( "Q" ) //cancel + ; + + p[PasswordPromptPattern] = QLatin1String("Enter password \\(will not be echoed\\) :"); + } + + return p; +} + +bool CliPlugin::readListLine(const QString& line) +{ + static const QLatin1String archiveInfoDelimiter1("--"); // 7z 9.13+ + static const QLatin1String archiveInfoDelimiter2("----"); // 7z 9.04 + static const QLatin1String entryInfoDelimiter("----------"); + + switch (m_state) { + case ReadStateHeader: + if (line.startsWith(QLatin1String("Listing archive:"))) { + kDebug() << "Archive name: " + << line.right(line.size() - 16).trimmed(); + } else if ((line == archiveInfoDelimiter1) || + (line == archiveInfoDelimiter2)) { + m_state = ReadStateArchiveInformation; + } else if (line.contains(QLatin1String( "Error:" ))) { + kDebug() << line.mid(6); + } + break; + + case ReadStateArchiveInformation: + if (line == entryInfoDelimiter) { + m_state = ReadStateEntryInformation; + } else if (line.startsWith(QLatin1String("Type ="))) { + const QString type = line.mid(7).trimmed(); + kDebug() << "Archive type: " << type; + + if (type == QLatin1String("7z")) { + m_archiveType = ArchiveType7z; + } else if (type == QLatin1String("BZip2")) { + m_archiveType = ArchiveTypeBZip2; + } else if (type == QLatin1String("GZip")) { + m_archiveType = ArchiveTypeGZip; + } else if (type == QLatin1String("Tar")) { + m_archiveType = ArchiveTypeTar; + } else if (type == QLatin1String("Zip")) { + m_archiveType = ArchiveTypeZip; + } else { + // Should not happen + kWarning() << "Unsupported archive type"; + return false; + } + } + + break; + + case ReadStateEntryInformation: + if (line.startsWith(QLatin1String("Path ="))) { + const QString entryFilename = + QDir::fromNativeSeparators(line.mid(6).trimmed()); + m_currentArchiveEntry.clear(); + m_currentArchiveEntry[FileName] = entryFilename; + m_currentArchiveEntry[InternalID] = entryFilename; + } else if (line.startsWith(QLatin1String("Size = "))) { + m_currentArchiveEntry[ Size ] = line.mid(7).trimmed(); + } else if (line.startsWith(QLatin1String("Packed Size = "))) { + // #236696: 7z files only show a single Packed Size value + // corresponding to the whole archive. + if (m_archiveType != ArchiveType7z) { + m_currentArchiveEntry[CompressedSize] = line.mid(14).trimmed(); + } + } else if (line.startsWith(QLatin1String("Modified = "))) { + m_currentArchiveEntry[ Timestamp ] = + QDateTime::fromString(line.mid(11).trimmed(), + QLatin1String( "yyyy-MM-dd hh:mm:ss" )); + } else if (line.startsWith(QLatin1String("Attributes = "))) { + const QString attributes = line.mid(13).trimmed(); + + const bool isDirectory = attributes.startsWith(QLatin1Char( 'D' )); + m_currentArchiveEntry[ IsDirectory ] = isDirectory; + if (isDirectory) { + const QString directoryName = + m_currentArchiveEntry[FileName].toString(); + if (!directoryName.endsWith(QLatin1Char( '/' ))) { + const bool isPasswordProtected = (line.at(12) == QLatin1Char( '+' )); + m_currentArchiveEntry[FileName] = + m_currentArchiveEntry[InternalID] = QString(directoryName + QLatin1Char( '/' )); + m_currentArchiveEntry[ IsPasswordProtected ] = + isPasswordProtected; + } + } + + m_currentArchiveEntry[ Permissions ] = attributes.mid(1); + } else if (line.startsWith(QLatin1String("CRC = "))) { + m_currentArchiveEntry[ CRC ] = line.mid(6).trimmed(); + } else if (line.startsWith(QLatin1String("Method = "))) { + m_currentArchiveEntry[ Method ] = line.mid(9).trimmed(); + } else if (line.startsWith(QLatin1String("Encrypted = ")) && + line.size() >= 13) { + m_currentArchiveEntry[ IsPasswordProtected ] = (line.at(12) == QLatin1Char( '+' )); + } else if (line.startsWith(QLatin1String("Block = "))) { + if (m_currentArchiveEntry.contains(FileName)) { + emit entry(m_currentArchiveEntry); + } + } + break; + } + + return true; +} + +KERFUFFLE_EXPORT_PLUGIN(CliPlugin) + +#include "cliplugin.moc" diff --git a/ark/plugins/cli7zplugin/cliplugin.h b/ark/plugins/cli7zplugin/cliplugin.h new file mode 100644 index 00000000..9f122e4a --- /dev/null +++ b/ark/plugins/cli7zplugin/cliplugin.h @@ -0,0 +1,61 @@ +/* + * ark -- archiver for the KDE project + * + * Copyright (C) 2009 Harald Hvaal + * Copyright (C) 2009-2010 Raphael Kubo da Costa + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + + +#ifndef CLIPLUGIN_H +#define CLIPLUGIN_H + +#include "kerfuffle/cliinterface.h" + +class CliPlugin : public Kerfuffle::CliInterface +{ + Q_OBJECT + +public: + explicit CliPlugin(QObject *parent, const QVariantList & args); + virtual ~CliPlugin(); + +protected: + virtual Kerfuffle::ParameterList parameterList() const; + virtual bool readListLine(const QString &line); + +private: + enum ArchiveType { + ArchiveType7z = 0, + ArchiveTypeBZip2, + ArchiveTypeGZip, + ArchiveTypeTar, + ArchiveTypeZip + }; + + enum ReadState { + ReadStateHeader = 0, + ReadStateArchiveInformation, + ReadStateEntryInformation + }; + + ArchiveType m_archiveType; + Kerfuffle::ArchiveEntry m_currentArchiveEntry; + ReadState m_state; +}; + +#endif // CLIPLUGIN_H diff --git a/ark/plugins/cli7zplugin/kerfuffle_cli7z.desktop.cmake b/ark/plugins/cli7zplugin/kerfuffle_cli7z.desktop.cmake new file mode 100644 index 00000000..29b355af --- /dev/null +++ b/ark/plugins/cli7zplugin/kerfuffle_cli7z.desktop.cmake @@ -0,0 +1,69 @@ +[Desktop Entry] +Type=Service +X-KDE-ServiceTypes=Kerfuffle/Plugin +X-KDE-Library=kerfuffle_cli7z +X-KDE-PluginInfo-Author=Harald Hvaal +X-KDE-PluginInfo-Email=haraldhv@stud.ntnu.no +X-KDE-PluginInfo-Name=kerfuffle_7z +X-KDE-PluginInfo-Version=0.0.1 +X-KDE-PluginInfo-Website=http://www.kde.org +X-KDE-PluginInfo-License=GPLv2+ +X-KDE-Priority=120 +X-KDE-Kerfuffle-APIRevision=1 +X-KDE-Kerfuffle-ReadWrite=true +Name=7zip archive plugin +Name[ar]=ملحق أرشيف 7zip +Name[ast]=Complementu d'archivu comprimíu 7zip +Name[bg]=Приставка за архиви 7zip +Name[bs]=Priključak 7z arhiva +Name[ca]=Connector per arxius 7zip +Name[ca@valencia]=Connector per arxius 7zip +Name[cs]=Modul pro archiv 7zip +Name[da]=Plugin til 7zip-arkiver +Name[de]=7zip-Archiv-Modul +Name[el]=Πρόσθετο αρχειοθήκης 7zip +Name[en_GB]=7zip archive plugin +Name[es]=Complemento de archivo comprimido 7zip +Name[et]=7zip arhiivi plugin +Name[eu]=7zip artxiboen plugina +Name[fi]=7zip-pakkaustuki +Name[fr]=Module externe pour archive « 7zip » +Name[ga]=Breiseán cartlainne 7zip +Name[gl]=Extensión de arquivo de 7zip +Name[hr]=Arhivni priključak 7zip +Name[hu]=7zip modul +Name[ia]=Plugin de archivar 7zip +Name[id]=Pengaya arsip 7zip +Name[it]=estensione per archivi 7zip +Name[ja]=7zip アーカイブ用プラグイン +Name[kk]=7zip архив плагині +Name[km]=កម្មវិធី​ជំនួយ​ប័ណ្ណសារ 7zip +Name[ko]=7zip 압축 플러그인 +Name[lt]=7zip archyvo priedas +Name[lv]=7zip arhīvu spraudnis +Name[mr]=7ZIP संग्रह प्लगइन +Name[nb]=Programtillegg for 7zip-arkiv +Name[nds]=7zip-Archievmoduul +Name[nl]=7zip-archiefplug-in +Name[nn]=7zip-arkivtillegg +Name[pa]=7zip ਅਕਾਇਵ ਪਲੱਗਇਨ +Name[pl]=Wtyczka archiwów 7zip +Name[pt]='Plugin' de pacotes '7zip' +Name[pt_BR]=Plugin de arquivos 7zip +Name[ro]=Modul de arhivă 7zip +Name[ru]=Поддержка архивов 7zip +Name[sk]=Modul 7zip archívu +Name[sl]=Vstavek za arhive 7zip +Name[sq]=7zip arkiv plugin +Name[sr]=Прикључак 7зип архива +Name[sr@ijekavian]=Прикључак 7зип архива +Name[sr@ijekavianlatin]=Priključak 7zip arhiva +Name[sr@latin]=Priključak 7zip arhiva +Name[sv]=Insticksprogram för 7zip arkiv +Name[th]=ส่วนเสริมการจัดการแฟ้มจัดเก็บแบบ 7zip +Name[tr]=7zip arşivi eklentisi +Name[uk]=Додаток для архівів 7zip +Name[x-test]=xx7zip archive pluginxx +Name[zh_CN]=7zip 归档插件 +Name[zh_TW]=7zip 壓縮檔外掛程式 +MimeType=@SUPPORTED_CLI7Z_MIMETYPES@ diff --git a/ark/plugins/clilhaplugin/CMakeLists.txt b/ark/plugins/clilhaplugin/CMakeLists.txt new file mode 100644 index 00000000..07916306 --- /dev/null +++ b/ark/plugins/clilhaplugin/CMakeLists.txt @@ -0,0 +1,21 @@ +########### next target ############### + +set(SUPPORTED_CLILHA_MIMETYPES "application/lha;application/x-lha;application/maclha;") + +set(kerfuffle_cli_SRCS cliplugin.cpp) + +kde4_add_plugin(kerfuffle_clilha ${kerfuffle_cli_SRCS}) + +target_link_libraries(kerfuffle_clilha ${KDE4_KDECORE_LIBS} ${KDE4_KIO_LIBS} kerfuffle ) + +configure_file( + ${CMAKE_CURRENT_SOURCE_DIR}/kerfuffle_clilha.desktop.cmake + ${CMAKE_CURRENT_BINARY_DIR}/kerfuffle_clilha.desktop +) + +########### install files ############### + +install(TARGETS kerfuffle_clilha DESTINATION ${PLUGIN_INSTALL_DIR} ) +install( FILES ${CMAKE_CURRENT_BINARY_DIR}/kerfuffle_clilha.desktop DESTINATION ${SERVICES_INSTALL_DIR} ) + +set(SUPPORTED_ARK_MIMETYPES "${SUPPORTED_ARK_MIMETYPES}${SUPPORTED_CLILHA_MIMETYPES}" PARENT_SCOPE) diff --git a/ark/plugins/clilhaplugin/cliplugin.cpp b/ark/plugins/clilhaplugin/cliplugin.cpp new file mode 100644 index 00000000..5c2aa6e3 --- /dev/null +++ b/ark/plugins/clilhaplugin/cliplugin.cpp @@ -0,0 +1,144 @@ +/* + * ark -- archiver for the KDE project + * + * Copyright (C) 2011 Intzoglou Theofilos + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "cliplugin.h" + +#include "kerfuffle/kerfuffle_export.h" +#include +#include +#include + +using namespace Kerfuffle; + +CliPlugin::CliPlugin(QObject *parent, const QVariantList &args) + : CliInterface(parent, args), + m_status(Header), + m_firstLine(true) +{ +} + +CliPlugin::~CliPlugin() +{ +} + +ParameterList CliPlugin::parameterList() const +{ + static ParameterList p; + if (p.isEmpty()) { + p[CaptureProgress] = true; + p[ListProgram] = p[ExtractProgram] = p[DeleteProgram] = p[AddProgram] = QStringList() << QLatin1String("lha"); + + p[ListArgs] = QStringList() << QLatin1String("v") << QLatin1String("-v") << QLatin1String("$Archive"); + p[ExtractArgs] = QStringList() << QLatin1String("e") << QLatin1String("-v") << QLatin1String("$PreservePathSwitch") << QLatin1String("$Archive") << QLatin1String("$Files"); + + p[DeleteArgs] = QStringList() << QLatin1String("d") << QLatin1String("-v") << QLatin1String("$Archive") << QLatin1String("$Files"); + + p[FileExistsExpression] = QLatin1String("^(.+) OverWrite \\?"); + p[FileExistsMode] = 1; // Watch for messages in stdout + p[FileExistsInput] = QStringList() + << QLatin1String("Y") //overwrite + << QLatin1String("N") //skip + << QLatin1String("A") //overwrite all + << QLatin1String("S") //autoskip + ; + + p[AddArgs] = QStringList() << QLatin1String("a") << QLatin1String("-v") << QLatin1String("$Archive") << QLatin1String("$Files"); + + p[ExtractionFailedPatterns] = QStringList() << QLatin1String("Error"); + p[PreservePathSwitch] = QStringList() << QLatin1String( "" ) << QLatin1String( "-i" ); + } + return p; +} + +bool CliPlugin::readListLine(const QString &line) +{ + const QString m_headerString = QLatin1String("----------"); + + switch(m_status) { + case Header: + if (line.startsWith(m_headerString)) { + m_status = Entry; + m_firstLine = true; + } + break; + case Entry: + const QStringList entryList = line.split(QLatin1Char(' '), QString::SkipEmptyParts); + + if (m_firstLine) { // This line will contain the filename + if (entryList.count() == 8) { // End of entries + m_status = Header; + } + else { + m_internalId = line; + m_firstLine = false; + } + } + else { // This line contains the rest of the information + ArchiveEntry e; + + if (!entryList[0].startsWith(QLatin1Char('['))) { + e[Permissions] = entryList[0]; + } + + e[IsDirectory] = m_internalId.endsWith(QLatin1Char('/')); + m_entryFilename = m_internalId; + e[FileName] = m_entryFilename; + e[InternalID] = m_internalId; + + if (entryList.count() == 9) { // UID/GID is missing + e[CompressedSize] = entryList[1]; + e[Size] = entryList[2]; + e[Ratio] = entryList[3]; + e[Method] = entryList[4]; + e[CRC] = entryList[5]; + + QDateTime timestamp( + QDate::fromString(entryList[6], QLatin1String("yyyy-MM-dd")), + QTime::fromString(entryList[7], QLatin1String("HH:mm:ss"))); + e[Timestamp] = timestamp; + emit entry(e); + } + else if (entryList.count() == 10) { // All info is available + const QStringList ownerList = entryList[1].split(QLatin1Char('/')); // Separate uid from gui + e[Owner] = ownerList.at(0); + e[Group] = ownerList.at(1); + e[CompressedSize] = entryList[2]; + e[Size] = entryList[3]; + e[Ratio] = entryList[4]; + e[Method] = entryList[5]; + e[CRC] = entryList[6]; + + QDateTime timestamp( + QDate::fromString(entryList[7], QLatin1String("yyyy-MM-dd")), + QTime::fromString(entryList[8], QLatin1String("HH:mm:ss"))); + e[Timestamp] = timestamp; + emit entry(e); + } + + m_firstLine = true; + } + break; + } + return true; +} + +KERFUFFLE_EXPORT_PLUGIN(CliPlugin) + diff --git a/ark/plugins/clilhaplugin/cliplugin.h b/ark/plugins/clilhaplugin/cliplugin.h new file mode 100644 index 00000000..10aedad9 --- /dev/null +++ b/ark/plugins/clilhaplugin/cliplugin.h @@ -0,0 +1,49 @@ +/* + * ark -- archiver for the KDE project + * + * Copyright (C) 2011 Intzoglou Theofilos + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef CLIPLUGIN_H +#define CLIPLUGIN_H + +#include "kerfuffle/cliinterface.h" + +class CliPlugin : public Kerfuffle::CliInterface +{ + Q_OBJECT + +public: + explicit CliPlugin(QObject *parent, const QVariantList &args); + virtual ~CliPlugin(); + + virtual Kerfuffle::ParameterList parameterList() const; + + virtual bool readListLine(const QString &line); + +private: + enum { + Header = 0, + Entry + } m_status; + + QString m_entryFilename; + QString m_internalId; + bool m_firstLine; +}; + +#endif // CLIPLUGIN_H diff --git a/ark/plugins/clilhaplugin/kerfuffle_clilha.desktop.cmake b/ark/plugins/clilhaplugin/kerfuffle_clilha.desktop.cmake new file mode 100644 index 00000000..20ae991d --- /dev/null +++ b/ark/plugins/clilhaplugin/kerfuffle_clilha.desktop.cmake @@ -0,0 +1,62 @@ +[Desktop Entry] +Type=Service +X-KDE-ServiceTypes=Kerfuffle/Plugin +X-KDE-Library=kerfuffle_clilha +X-KDE-PluginInfo-Author=Theofilos Intzoglou +X-KDE-PluginInfo-Email=int.teo@gmail.com +X-KDE-PluginInfo-Name=kerfuffle_clilha +X-KDE-PluginInfo-Version=0.0.1 +X-KDE-PluginInfo-Website=http://www.kde.org +X-KDE-PluginInfo-License=GPLv2+ +X-KDE-Priority=100 +X-KDE-Kerfuffle-APIRevision=1 +X-KDE-Kerfuffle-ReadWrite=true +Name=lha archive plugin +Name[ar]=ملحَق أرشيفات IHA +Name[bg]=Приставка за архиви lha +Name[bs]=lha arhivni dodatak +Name[ca]=Connector per arxius LHA +Name[ca@valencia]=Connector per arxius LHA +Name[cs]=Modul pro archiv lha +Name[da]=Plugin til lha-arkiver +Name[de]=lha-Archiv-Modul +Name[el]=πρόσθετο αρχειοθήκης lha +Name[en_GB]=lha archive plugin +Name[es]=Complemento de archivo comprimido LHA +Name[et]=lha-arhiivi plugin +Name[eu]=LHA artxiboen plugina +Name[fi]=lha-pakkaustuki +Name[fr]=Module externe d'archive « lha » +Name[ga]=Breiseán cartlainne lha +Name[gl]=Complemento para arquivos LHA +Name[hu]=lha modul +Name[ia]=plugin de archivar lha +Name[it]=estensione per archivi lha +Name[ja]=lha アーカイブ用プラグイン +Name[kk]=lha архив плагині +Name[km]=កម្មវិធី​ជំនួយ​ប័ណ្ណសារ lha +Name[ko]=LHA 압축 플러그인 +Name[lt]=lha archyvo priedas +Name[mr]=lha संग्रह प्लगइन +Name[nb]=Programtillegg for lha-arkiv +Name[nds]=LHA-Archievmoduul +Name[nl]=lha-archiefplug-in +Name[pa]=lha ਅਕਾਇਵ ਪਲੱਗਇਨ +Name[pl]=Wtyczka archiwów lha +Name[pt]='Plugin' de pacotes LHA +Name[pt_BR]=Plugin de arquivos LHA +Name[ro]=Modul de arhivă lha +Name[ru]=Поддержка архивов LHA +Name[sk]=Modul lha archívu +Name[sl]=Vstavek za arhive lha +Name[sr]=Прикључак ЛХА архива +Name[sr@ijekavian]=Прикључак ЛХА архива +Name[sr@ijekavianlatin]=Priključak LHA arhiva +Name[sr@latin]=Priključak LHA arhiva +Name[sv]=Insticksprogram för LHA-arkiv +Name[tr]=lha arşiv eklentisi +Name[uk]=Додаток для архівів LHA +Name[x-test]=xxlha archive pluginxx +Name[zh_CN]=lha 归档插件 +Name[zh_TW]=lha 壓縮檔外掛程式 +MimeType=@SUPPORTED_CLILHA_MIMETYPES@ diff --git a/ark/plugins/cliplugin-example/CMakeLists.txt b/ark/plugins/cliplugin-example/CMakeLists.txt new file mode 100644 index 00000000..95836864 --- /dev/null +++ b/ark/plugins/cliplugin-example/CMakeLists.txt @@ -0,0 +1,7 @@ +set(kerfuffle_cli_SRCS cliplugin.cpp) +kde4_add_plugin(kerfuffle_cli ${kerfuffle_cli_SRCS}) +target_link_libraries(kerfuffle_cli ${KDE4_KDECORE_LIBS} ${KDE4_KIO_LIBS} kerfuffle) + +install(TARGETS kerfuffle_cli DESTINATION ${PLUGIN_INSTALL_DIR}) +install(FILES kerfuffle_cli.desktop DESTINATION ${SERVICES_INSTALL_DIR}) + diff --git a/ark/plugins/cliplugin-example/cliplugin.cpp b/ark/plugins/cliplugin-example/cliplugin.cpp new file mode 100644 index 00000000..c5ca4e08 --- /dev/null +++ b/ark/plugins/cliplugin-example/cliplugin.cpp @@ -0,0 +1,148 @@ +/* + * ark -- archiver for the KDE project + * + * Copyright (C) 2008 Claudio Bantaloukas + * Copyright (C) 2007 Henrique Pinto + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "cliplugin.h" +#include "kerfuffle/kerfuffle_export.h" + +#include + +#include +#include +#include + +CliPlugin::CliPlugin(QObject *parent, const QVariantList &args) + : CliInterface(parent, args), + m_isFirstLine(true), + m_incontent(false), + m_isPasswordProtected(false) +{ +} + +CliPlugin::~CliPlugin() +{ +} + +ParameterList CliPlugin::parameterList() const +{ + static ParameterList p; + + if (p.isEmpty()) { + p[CaptureProgress] = true; + p[ListProgram] = p[ExtractProgram] = p[DeleteProgram] = p[AddProgram] = QLatin1String("rar"); + + p[ListArgs] = QStringList() << QLatin1String("v") << QLatin1String("-c-") << QLatin1String("$Archive"); + p[ExtractArgs] = QStringList() << QLatin1String("-p-") << QLatin1String("$PreservePathSwitch") << QLatin1String("$PasswordSwitch") << QLatin1String("$RootNodeSwitch") << QLatin1String("$Archive") << QLatin1String("$Files"); + p[PreservePathSwitch] = QStringList() << QLatin1String("x") << QLatin1String("e"); + p[RootNodeSwitch] = QStringList() << QLatin1String("-ap$Path"); + p[PasswordSwitch] = QStringList() << QLatin1String("-p$Password"); + + p[DeleteArgs] = QStringList() << QLatin1String("d") << QLatin1String("$Archive") << QLatin1String("$Files"); + + p[FileExistsExpression] = QLatin1String("^(.+) already exists. Overwrite it"); + p[FileExistsInput] = QStringList() + << QLatin1String("Y") //overwrite + << QLatin1String("N") //skip + << QLatin1String("A") //overwrite all + << QLatin1String("E") //autoskip + << QLatin1String("Q") //cancel + ; + + p[AddArgs] = QStringList() << QLatin1String("a") << QLatin1String("$Archive") << QLatin1String("$Files"); + + p[WrongPasswordPatterns] = QStringList() << QLatin1String("password incorrect"); + p[ExtractionFailedPatterns] = QStringList() << QLatin1String("CRC failed"); + } + + return p; +} + +bool CliPlugin::readListLine(const QString &line) +{ + const QString m_headerString = QLatin1String("-----------------------------------------"); + + // skip the heading + if (!m_incontent) { + if (line.startsWith(m_headerString)) { + m_incontent = true; + } + return true; + } + + // catch final line + if (line.startsWith(m_headerString)) { + m_incontent = false; + return true; + } + + // rar gives one line for the filename and a line after it with some file properties + if (m_isFirstLine) { + m_internalId = line.trimmed(); + //m_entryFilename.chop(1); // handle newline + if (!m_internalId.isEmpty() && m_internalId.at(0) == QLatin1Char('*')) { + m_isPasswordProtected = true; + m_internalId.remove(0, 1); // and the spaces in front + } else + m_isPasswordProtected = false; + + m_isFirstLine = false; + return true; + } + + QStringList fileprops = line.split(QLatin1Char(' '), QString::SkipEmptyParts); + m_internalId = QDir::fromNativeSeparators(m_internalId); + bool isDirectory = (bool)(fileprops[ 5 ].contains(QLatin1Char('d'), Qt::CaseInsensitive)); + + QDateTime ts(QDate::fromString(fileprops[ 3 ], QLatin1String("dd-MM-yy")), + QTime::fromString(fileprops[ 4 ], QLatin1String("hh:mm"))); + // rar output date with 2 digit year but QDate takes is as 19?? + // let's take 1950 is cut-off; similar to KDateTime + if (ts.date().year() < 1950) { + ts = ts.addYears(100); + } + + m_entryFilename = m_internalId; + if (isDirectory && !m_internalId.endsWith(QLatin1Char('/'))) { + m_entryFilename += QLatin1Char('/'); + } + + //kDebug() << m_entryFilename << " : " << fileprops; + ArchiveEntry e; + e[ FileName ] = m_entryFilename; + e[ InternalID ] = m_internalId; + e[ Size ] = fileprops[ 0 ]; + e[ CompressedSize] = fileprops[ 1 ]; + e[ Ratio ] = fileprops[ 2 ]; + e[ Timestamp ] = ts; + e[ IsDirectory ] = isDirectory; + e[ Permissions ] = fileprops[ 5 ].remove(0, 1); + e[ CRC ] = fileprops[ 6 ]; + e[ Method ] = fileprops[ 7 ]; + e[ Version ] = fileprops[ 8 ]; + e[ IsPasswordProtected] = m_isPasswordProtected; + kDebug() << "Added entry: " << e; + + emit entry(e); + m_isFirstLine = true; + return true; +} + +KERFUFFLE_EXPORT_PLUGIN(CliPlugin) diff --git a/ark/plugins/cliplugin-example/cliplugin.h b/ark/plugins/cliplugin-example/cliplugin.h new file mode 100644 index 00000000..21e935d6 --- /dev/null +++ b/ark/plugins/cliplugin-example/cliplugin.h @@ -0,0 +1,45 @@ +/* + * ark -- archiver for the KDE project + * + * Copyright (C) 2008 Claudio Bantaloukas + * Copyright (C) 2007 Henrique Pinto + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef CLIPLUGIN_H +#define CLIPLUGIN_H + +#include "kerfuffle/cliinterface.h" + +using namespace Kerfuffle; + +class CliPlugin: public CliInterface +{ +public: + explicit CliPlugin(QObject *parent = 0, const QVariantList &args = QVariantList()); + virtual ~CliPlugin(); + + virtual ParameterList parameterList() const; + bool readListLine(const QString &line); + +private: + bool m_isFirstLine, m_incontent, m_isPasswordProtected; + QString m_entryFilename, m_internalId; + +}; + +#endif // CLIPLUGIN_H diff --git a/ark/plugins/cliplugin-example/kerfuffle_cli.desktop b/ark/plugins/cliplugin-example/kerfuffle_cli.desktop new file mode 100644 index 00000000..51de02de --- /dev/null +++ b/ark/plugins/cliplugin-example/kerfuffle_cli.desktop @@ -0,0 +1,66 @@ +[Desktop Entry] +Type=Service +X-KDE-ServiceTypes=Kerfuffle/Plugin +X-KDE-Library=TODO kerfuffle_cli +X-KDE-PluginInfo-Author=TODO Your name +X-KDE-PluginInfo-Email=TODO Your email +X-KDE-PluginInfo-Name=TODO kerfuffle_cli +X-KDE-PluginInfo-Version=0.0.1 +X-KDE-PluginInfo-Website=http://www.kde.org +X-KDE-PluginInfo-License=GPLv2+ +X-KDE-Priority=100 +X-KDE-Kerfuffle-APIRevision=1 +X-KDE-Kerfuffle-ReadWrite=true +Name=TODO archive plugin +Name[ar]=ملحق أرشيف TODO +Name[ast]=Complementu d'archivu comprimíu 'TODO' +Name[bs]=(URADI) priključak arhiva +Name[ca]=Connector per arxius TODO +Name[ca@valencia]=Connector per arxius TODO +Name[cs]=Modul pro archiv TODO +Name[da]=Plugin til TODO-arkiver +Name[de]=TODO-Archiv-Modul +Name[el]=πρόσθετο αρχειοθήκης προς υλοποίηση +Name[en_GB]=TODO archive plugin +Name[es]=Complemento de archivo comprimido XXX +Name[et]=TODO arhiivi plugin +Name[eu]=TODO artxiboen plugina +Name[fi]=TODO-pakkaustuki +Name[fr]=Module d'archive « À faire » +Name[ga]=Breiseán cartlainne TODO +Name[gl]=Extensión de arquivo TODO +Name[hr]=Arhivni priključak TODO +Name[hu]=TODO modul +Name[ia]=plugin de archivar DEFACER (TODO) +Name[id]=Pengaya arsip TODO +Name[it]=estensione per archivi TODO +Name[kk]=TODO архив плагині +Name[km]=កម្មវិធី​ជំនួយ​ប័ណ្ណសារ​របស់​ការងារ​ត្រូវ​ធ្វើ +Name[ko]=TODO 압축 플러그인 +Name[lt]=PADARYTI archyvų priedas +Name[lv]=TODO arhīvu spraudnis +Name[mr]=TODO संग्रह प्लगइन +Name[nb]=Programtillegg for gjøremålsarkiv +Name[nds]=TODO-Archievmoduul +Name[nl]=TODO-archiefplug-in +Name[nn]=TODO-arkivtillegg +Name[pl]=Wtyczka archiwów do zrobienia +Name[pt]='Plugin' de pacotes POR FAZER +Name[pt_BR]=Plugin de arquivos A FAZER +Name[ro]=Modul de arhivă TODO +Name[ru]=TODO модуль архивирования +Name[sk]=TODO modul archívu +Name[sl]=Vstavek za arhive NAREDI +Name[sq]=TODO arkiv plugin +Name[sr]=Прикључак TODO архива +Name[sr@ijekavian]=Прикључак TODO архива +Name[sr@ijekavianlatin]=Priključak TODO arhiva +Name[sr@latin]=Priključak TODO arhiva +Name[sv]=Insticksprogram för ATT GÖRA arkiv +Name[th]=ส่วนเสริมการจัดการแฟ้มจัดเก็บแบบ TODO +Name[tr]=TODO arşivi eklentisi +Name[uk]=Ще не створений додаток архівів +Name[x-test]=xxTODO archive pluginxx +Name[zh_CN]=TODO 归档插件 +Name[zh_TW]=TODO 歸檔外掛程式 +MimeType=TODO; diff --git a/ark/plugins/clirarplugin/CMakeLists.txt b/ark/plugins/clirarplugin/CMakeLists.txt new file mode 100644 index 00000000..f0ec0d0d --- /dev/null +++ b/ark/plugins/clirarplugin/CMakeLists.txt @@ -0,0 +1,23 @@ +########### next target ############### + +set(SUPPORTED_CLIRAR_MIMETYPES "application/x-rar;") + +set(kerfuffle_clirar_SRCS cliplugin.cpp) + +kde4_add_plugin(kerfuffle_clirar ${kerfuffle_clirar_SRCS}) + +target_link_libraries(kerfuffle_clirar ${KDE4_KDECORE_LIBS} ${KDE4_KIO_LIBS} kerfuffle ) + +configure_file( + ${CMAKE_CURRENT_SOURCE_DIR}/kerfuffle_clirar.desktop.cmake + ${CMAKE_CURRENT_BINARY_DIR}/kerfuffle_clirar.desktop +) + +########### install files ############### + +install(TARGETS kerfuffle_clirar DESTINATION ${PLUGIN_INSTALL_DIR} ) +install( FILES ${CMAKE_CURRENT_BINARY_DIR}/kerfuffle_clirar.desktop DESTINATION ${SERVICES_INSTALL_DIR} ) + +add_subdirectory(tests) + +set(SUPPORTED_ARK_MIMETYPES "${SUPPORTED_ARK_MIMETYPES}${SUPPORTED_CLIRAR_MIMETYPES}" PARENT_SCOPE) diff --git a/ark/plugins/clirarplugin/cliplugin.cpp b/ark/plugins/clirarplugin/cliplugin.cpp new file mode 100644 index 00000000..f5f7b184 --- /dev/null +++ b/ark/plugins/clirarplugin/cliplugin.cpp @@ -0,0 +1,362 @@ +/* + * ark -- archiver for the KDE project + * + * Copyright (C) 2009 Harald Hvaal + * Copyright (C) 2010-2011,2014 Raphael Kubo da Costa + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "cliplugin.h" +#include "kerfuffle/cliinterface.h" +#include "kerfuffle/kerfuffle_export.h" + +#include + +#include +#include +#include +#include + +using namespace Kerfuffle; + +CliPlugin::CliPlugin(QObject *parent, const QVariantList& args) + : CliInterface(parent, args) + , m_parseState(ParseStateColumnDescription1) + , m_isPasswordProtected(false) + , m_remainingIgnoredSubHeaderLines(0) + , m_remainingIgnoredDetailsLines(0) + , m_isUnrarFree(false) + , m_isUnrarVersion5(false) +{ +} + +CliPlugin::~CliPlugin() +{ +} + +// #272281: the proprietary unrar program does not like trailing '/'s +// in directories passed to it when extracting only part of +// the files in an archive. +QString CliPlugin::escapeFileName(const QString &fileName) const +{ + if (fileName.endsWith(QLatin1Char('/'))) { + return fileName.left(fileName.length() - 1); + } + + return fileName; +} + +ParameterList CliPlugin::parameterList() const +{ + static ParameterList p; + + if (p.isEmpty()) { + p[CaptureProgress] = true; + p[ListProgram] = p[ExtractProgram] = QStringList() << QLatin1String( "unrar" ); + p[DeleteProgram] = p[AddProgram] = QStringList() << QLatin1String( "rar" ); + + p[ListArgs] = QStringList() << QLatin1String( "vt" ) << QLatin1String( "-c-" ) << QLatin1String( "-v" ) << QLatin1String( "$Archive" ); + p[ExtractArgs] = QStringList() << QLatin1String( "-kb" ) << QLatin1String( "-p-" ) + << QLatin1String( "$PreservePathSwitch" ) + << QLatin1String( "$PasswordSwitch" ) + << QLatin1String( "$RootNodeSwitch" ) + << QLatin1String( "$Archive" ) + << QLatin1String( "$Files" ); + p[PreservePathSwitch] = QStringList() << QLatin1String( "x" ) << QLatin1String( "e" ); + p[RootNodeSwitch] = QStringList() << QLatin1String( "-ap$Path" ); + p[PasswordSwitch] = QStringList() << QLatin1String( "-p$Password" ); + + p[DeleteArgs] = QStringList() << QLatin1String( "d" ) << QLatin1String( "$Archive" ) << QLatin1String( "$Files" ); + + p[FileExistsExpression] = QLatin1String( "^(.+) already exists. Overwrite it" ); + p[FileExistsInput] = QStringList() + << QLatin1String( "Y" ) //overwrite + << QLatin1String( "N" ) //skip + << QLatin1String( "A" ) //overwrite all + << QLatin1String( "E" ) //autoskip + << QLatin1String( "Q" ) //cancel + ; + + p[AddArgs] = QStringList() << QLatin1String( "a" ) << QLatin1String( "$Archive" ) << QLatin1String( "$Files" ); + + p[PasswordPromptPattern] = QLatin1String("Enter password \\(will not be echoed\\) for"); + + p[WrongPasswordPatterns] = QStringList() << QLatin1String("password incorrect") << QLatin1String("wrong password"); + p[ExtractionFailedPatterns] = QStringList() << QLatin1String( "CRC failed" ) << QLatin1String( "Cannot find volume" ); + } + + return p; +} + +bool CliPlugin::readListLine(const QString &line) +{ + static const QLatin1String headerString("----------------------"); + static const QLatin1String subHeaderString("Data header type: "); + static const QLatin1String columnDescription1String(" Size Packed Ratio Date Time Attr CRC Meth Ver"); + static const QLatin1String columnDescription2String(" Host OS Solid Old"); // Only present in unrar-nonfree + + if (m_isUnrarVersion5) { + int colonPos = line.indexOf(QLatin1Char(':')); + if (colonPos == -1) { + if (m_entryFileName.isEmpty()) { + return true; + } + ArchiveEntry e; + + QString compressionRatio = m_entryDetails.value(QLatin1String("ratio")); + compressionRatio.chop(1); // Remove the '%' + + QString time = m_entryDetails.value(QLatin1String("mtime")); + // FIXME unrar 5 beta 8 seems to lack the seconds, or the trailing ,000 is not the milliseconds + QDateTime ts = QDateTime::fromString(time, QLatin1String("yyyy-MM-dd HH:mm,zzz")); + + bool isDirectory = m_entryDetails.value(QLatin1String("type")) == QLatin1String("Directory"); + if (isDirectory && !m_entryFileName.endsWith(QLatin1Char( '/' ))) { + m_entryFileName += QLatin1Char( '/' ); + } + + QString compression = m_entryDetails.value(QLatin1String("compression")); + int optionPos = compression.indexOf(QLatin1Char('-')); + if (optionPos != -1) { + e[Method] = compression.mid(optionPos); + e[Version] = compression.left(optionPos).trimmed(); + } else { + // no method specified + e[Method].clear(); + e[Version] = compression; + } + + m_isPasswordProtected = m_entryDetails.value(QLatin1String("flags")).contains(QLatin1String("encrypted")); + + e[FileName] = m_entryFileName; + e[InternalID] = m_entryFileName; + e[Size] = m_entryDetails.value(QLatin1String("size")); + e[CompressedSize] = m_entryDetails.value(QLatin1String("packed size")); + e[Ratio] = compressionRatio; + e[Timestamp] = ts; + e[IsDirectory] = isDirectory; + e[Permissions] = m_entryDetails.value(QLatin1String("attributes")); + e[CRC] = m_entryDetails.value(QLatin1String("crc32")); + e[IsPasswordProtected] = m_isPasswordProtected; + kDebug() << "Added entry: " << e; + + emit entry(e); + + m_entryFileName.clear(); + + return true; + } + + QString key = line.left(colonPos).trimmed().toLower(); + QString value = line.mid(colonPos + 2); + + if (key == QLatin1String("name")) { + m_entryFileName = value; + m_entryDetails.clear(); + return true; + } + + // in multivolume archives, the split CRC32 is denoted specially + if (key == QLatin1String("pack-crc32")) { + key = key.mid(5); + } + + m_entryDetails.insert(key, value); + + return true; + } + + switch (m_parseState) + { + case ParseStateColumnDescription1: + if (line.startsWith(QLatin1String("Details:"))) { + m_isUnrarVersion5 = true; + setListEmptyLines(true); + // no previously detected entry + m_entryFileName.clear(); + } + if (line.startsWith(columnDescription1String)) { + m_parseState = ParseStateColumnDescription2; + } + + break; + + case ParseStateColumnDescription2: + // #243273: We need a way to differentiate unrar and unrar-free, + // as their output for the "vt" option is different. + // Currently, we differ them by checking if "vt" produces + // two lines of column names before the header string, as + // only unrar does that (unrar-free always outputs one line + // for column names regardless of how verbose we tell it to + // be). + if (line.startsWith(columnDescription2String)) { + m_parseState = ParseStateHeader; + } else if (line.startsWith(headerString)) { + m_parseState = ParseStateEntryFileName; + m_isUnrarFree = true; + } + + break; + + case ParseStateHeader: + if (line.startsWith(headerString)) { + m_parseState = ParseStateEntryFileName; + } + + break; + + case ParseStateEntryFileName: + if (m_remainingIgnoredSubHeaderLines > 0) { + --m_remainingIgnoredSubHeaderLines; + return true; + } + + // #242071: The RAR file format has the concept of service headers, + // such as CMT (comments), STM (NTFS alternate data streams) + // and RR (recovery record). These service headers do no + // interest us, and ignoring them seems harmless (at least + // 7zip and WinRAR do not show them either). + if (line.startsWith(subHeaderString)) { + // subHeaderString's length is 18 + const QString subHeaderType(line.mid(18)); + + // XXX: If we ever support archive comments, this code must + // be changed, because the comments will be shown after + // a CMT subheader and will have an arbitrary number of lines + if (subHeaderType == QLatin1String("STM")) { + m_remainingIgnoredSubHeaderLines = 4; + } else { + m_remainingIgnoredSubHeaderLines = 3; + } + + kDebug() << "Found a subheader of type" << subHeaderType; + kDebug() << "The next" << m_remainingIgnoredSubHeaderLines + << "lines will be ignored"; + + return true; + } else if (line.startsWith(headerString)) { + m_parseState = ParseStateHeader; + + return true; + } + + m_isPasswordProtected = (line.at(0) == QLatin1Char( '*' )); + + // Start from 1 because the first character is either ' ' or '*' + m_entryFileName = QDir::fromNativeSeparators(line.mid(1)); + + m_parseState = ParseStateEntryDetails; + + break; + + case ParseStateEntryIgnoredDetails: + if (m_remainingIgnoredDetailsLines > 0) { + --m_remainingIgnoredDetailsLines; + return true; + } + m_parseState = ParseStateEntryFileName; + + break; + + case ParseStateEntryDetails: + if (line.startsWith(headerString)) { + m_parseState = ParseStateHeader; + return true; + } + + const QStringList details = line.split(QLatin1Char( ' ' ), + QString::SkipEmptyParts); + + QDateTime ts(QDate::fromString(details.at(3), + QLatin1String("dd-MM-yy")), + QTime::fromString(details.at(4), + QLatin1String("hh:mm"))); + + // unrar outputs dates with a 2-digit year but QDate takes it as 19?? + // let's take 1950 is cut-off; similar to KDateTime + if (ts.date().year() < 1950) { + ts = ts.addYears(100); + } + + bool isDirectory = ((details.at(5).at(0) == QLatin1Char( 'd' )) || + (details.at(5).at(1) == QLatin1Char( 'D' ))); + if (isDirectory && !m_entryFileName.endsWith(QLatin1Char( '/' ))) { + m_entryFileName += QLatin1Char( '/' ); + } + + // If the archive is a multivolume archive, a string indicating + // whether the archive's position in the volume is displayed + // instead of the compression ratio. + QString compressionRatio = details.at(2); + if ((compressionRatio == QLatin1String("<--")) || + (compressionRatio == QLatin1String("<->")) || + (compressionRatio == QLatin1String("-->"))) { + compressionRatio = QLatin1Char( '0' ); + } else { + compressionRatio.chop(1); // Remove the '%' + } + + // TODO: + // - Permissions differ depending on the system the entry was added + // to the archive. + // - unrar reports the ratio as ((compressed size * 100) / size); + // we consider ratio as (100 * ((size - compressed size) / size)). + ArchiveEntry e; + e[FileName] = m_entryFileName; + e[InternalID] = m_entryFileName; + e[Size] = details.at(0); + e[CompressedSize] = details.at(1); + e[Ratio] = compressionRatio; + e[Timestamp] = ts; + e[IsDirectory] = isDirectory; + e[Permissions] = details.at(5); + e[CRC] = details.at(6); + e[Method] = details.at(7); + e[Version] = details.at(8); + e[IsPasswordProtected] = m_isPasswordProtected; + kDebug() << "Added entry: " << e; + + // #314297: When RAR 3.x and RAR 4.x list a symlink, they output an + // extra line after the "Host OS/Solid/Old" one mentioning the + // target of the symlink in question. We are not interested in + // this line at the moment, so we just tell the parser to skip + // it. + if (e[Permissions].toString().startsWith(QLatin1Char('l'))) { + m_remainingIgnoredDetailsLines = 1; + } else { + m_remainingIgnoredDetailsLines = 0; + } + + emit entry(e); + + // #243273: unrar-free does not output the third file entry line, + // skip directly to parsing a new entry. + if (m_isUnrarFree) { + m_parseState = ParseStateEntryFileName; + } else { + m_parseState = ParseStateEntryIgnoredDetails; + } + + break; + } + + return true; +} + +KERFUFFLE_EXPORT_PLUGIN(CliPlugin) + +#include "cliplugin.moc" diff --git a/ark/plugins/clirarplugin/cliplugin.h b/ark/plugins/clirarplugin/cliplugin.h new file mode 100644 index 00000000..e11fca40 --- /dev/null +++ b/ark/plugins/clirarplugin/cliplugin.h @@ -0,0 +1,65 @@ +/* + * ark -- archiver for the KDE project + * + * Copyright (C) 2009 Harald Hvaal + * Copyright (C) 2009-2010 Raphael Kubo da Costa + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef CLIPLUGIN_H +#define CLIPLUGIN_H + +#include "kerfuffle/cliinterface.h" + +class CliPlugin : public Kerfuffle::CliInterface +{ + Q_OBJECT + +public: + explicit CliPlugin(QObject *parent, const QVariantList & args); + + virtual ~CliPlugin(); + + virtual QString escapeFileName(const QString &fileName) const; + + virtual Kerfuffle::ParameterList parameterList() const; + + virtual bool readListLine(const QString &line); + +private: + enum { + ParseStateColumnDescription1 = 0, + ParseStateColumnDescription2, + ParseStateHeader, + ParseStateEntryFileName, + ParseStateEntryDetails, + ParseStateEntryIgnoredDetails + } m_parseState; + + QString m_entryFileName; + QHash m_entryDetails; + + bool m_isPasswordProtected; + + int m_remainingIgnoredSubHeaderLines; + int m_remainingIgnoredDetailsLines; + + bool m_isUnrarFree; + bool m_isUnrarVersion5; +}; + +#endif // CLIPLUGIN_H diff --git a/ark/plugins/clirarplugin/kerfuffle_clirar.desktop.cmake b/ark/plugins/clirarplugin/kerfuffle_clirar.desktop.cmake new file mode 100644 index 00000000..3091d9d3 --- /dev/null +++ b/ark/plugins/clirarplugin/kerfuffle_clirar.desktop.cmake @@ -0,0 +1,69 @@ +[Desktop Entry] +Type=Service +X-KDE-ServiceTypes=Kerfuffle/Plugin +X-KDE-Library=kerfuffle_clirar +X-KDE-PluginInfo-Author=Harald Hvaal +X-KDE-PluginInfo-Email=haraldhv@stud.ntnu.no +X-KDE-PluginInfo-Name=kerfuffle_clirar +X-KDE-PluginInfo-Version=0.0.1 +X-KDE-PluginInfo-Website=http://www.kde.org +X-KDE-PluginInfo-License=GPLv2+ +X-KDE-Priority=120 +X-KDE-Kerfuffle-APIRevision=1 +X-KDE-Kerfuffle-ReadWrite=true +Name=RAR archive plugin +Name[ar]=ملحق أرشيف RAR +Name[ast]=Complementu d'archivu comprimíu RAR +Name[bg]=Приставка за архиви RAR +Name[bs]=Priključak RAR arhiva +Name[ca]=Connector per arxius RAR +Name[ca@valencia]=Connector per arxius RAR +Name[cs]=Modul pro archiv RAR +Name[da]=Plugin til RAR-arkiver +Name[de]=RAR-Archiv-Modul +Name[el]=πρόσθετο αρχειοθήκης RAR +Name[en_GB]=RAR archive plugin +Name[es]=Complemento de archivo comprimido RAR +Name[et]=RAR-arhiivi plugin +Name[eu]=RAR artxiboen plugina +Name[fi]=RAR-pakkaustuki +Name[fr]=Module externe d'archive « RAR » +Name[ga]=Breiseán cartlainne RAR +Name[gl]=Extensión de arquivo RAR +Name[hr]=Arhivni priključak RAR +Name[hu]=RAR modul +Name[ia]=plugin de archivar RAR +Name[id]=Pengaya arsip RAR +Name[it]=estensione per archivi RAR +Name[ja]=RAR アーカイブ用プラグイン +Name[kk]=RAR архив плагині +Name[km]=កម្មវិធី​ជំនួយ​ប័ណ្ណសារ RAR +Name[ko]=RAR 압축 플러그인 +Name[lt]=RAR archyvo priedas +Name[lv]=RAR arhīvu spraudnis +Name[mr]=RAR संग्रह प्लगइन +Name[nb]=Programtillegg for RAR-arkiv +Name[nds]=RAR-Archievmoduul +Name[nl]=RAR-archiefplug-in +Name[nn]=RAR-arkivtillegg +Name[pa]=RAR ਅਕਾਇਵ ਪਲੱਗਇਨ +Name[pl]=Wtyczka archiwów RAR +Name[pt]='Plugin' de pacotes RAR +Name[pt_BR]=Plugin de arquivos RAR +Name[ro]=Modul de arhivă RAR +Name[ru]=Поддержка архивов RAR +Name[sk]=Modul RAR archívu +Name[sl]=Vstavek za arhive RAR +Name[sq]=RAR arkiv plugin +Name[sr]=Прикључак РАР архива +Name[sr@ijekavian]=Прикључак РАР архива +Name[sr@ijekavianlatin]=Priključak RAR arhiva +Name[sr@latin]=Priključak RAR arhiva +Name[sv]=Insticksprogram för RAR-arkiv +Name[th]=ส่วนเสริมการจัดการแฟ้มจัดเก็บบีบอัดแบบ RAR +Name[tr]=RAR arşivi eklentisi +Name[uk]=Додаток для архівів RAR +Name[x-test]=xxRAR archive pluginxx +Name[zh_CN]=RAR 归档插件 +Name[zh_TW]=RAR 壓縮檔外掛程式 +MimeType=@SUPPORTED_CLIRAR_MIMETYPES@ diff --git a/ark/plugins/clirarplugin/tests/CMakeLists.txt b/ark/plugins/clirarplugin/tests/CMakeLists.txt new file mode 100644 index 00000000..5466c2f8 --- /dev/null +++ b/ark/plugins/clirarplugin/tests/CMakeLists.txt @@ -0,0 +1,4 @@ +set(RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + +kde4_add_unit_test(clirartest NOGUI clirartest.cpp ../cliplugin.cpp) +target_link_libraries(clirartest kerfuffle ${QT_QTTEST_LIBRARY} ${KDE4_KPARTS_LIBRARY}) diff --git a/ark/plugins/clirarplugin/tests/clirartest.cpp b/ark/plugins/clirarplugin/tests/clirartest.cpp new file mode 100644 index 00000000..622dd6c7 --- /dev/null +++ b/ark/plugins/clirarplugin/tests/clirartest.cpp @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2011,2014 Raphael Kubo da Costa + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ( INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION ) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "clirartest.h" +#include "../cliplugin.h" + +#include + +#include +#include + +QTEST_KDEMAIN_CORE(CliRarTest) + +using namespace Kerfuffle; + +/* + * Check that the plugin will not crash when reading corrupted archives, which + * have lines such as "Unexpected end of archive" or "??? - the file header is + * corrupt" instead of a file name and the header string after it. + * + * See bug 262857 and commit 2042997013432cdc6974f5b26d39893a21e21011. + */ +void CliRarTest::testReadCorruptedArchive() +{ + QVariantList args; + args.append(QLatin1String("DummyArchive.rar")); + + CliPlugin *rarPlugin = new CliPlugin(this, args); + Q_ASSERT(rarPlugin->open()); + + QFile unrarOutput(QLatin1String(KDESRCDIR "data/testReadCorruptedArchive.txt")); + Q_ASSERT(unrarOutput.open(QIODevice::ReadOnly)); + + QTextStream unrarStream(&unrarOutput); + while (!unrarStream.atEnd()) { + const QString line(unrarStream.readLine()); + Q_ASSERT(rarPlugin->readListLine(line)); + } + + rarPlugin->deleteLater(); +} + +/* + * Bug 314297: do not crash when a RAR archive has a symlink. + */ +void CliRarTest::testParseSymlink() +{ + QVariantList args; + args.append(QLatin1String("DummyArchive.rar")); + + CliPlugin *rarPlugin = new CliPlugin(this, args); + Q_ASSERT(rarPlugin->open()); + + QFile unrarOutput(QLatin1String(KDESRCDIR "data/testReadArchiveWithSymlink.txt")); + Q_ASSERT(unrarOutput.open(QIODevice::ReadOnly)); + + QTextStream unrarStream(&unrarOutput); + while (!unrarStream.atEnd()) { + const QString line(unrarStream.readLine()); + Q_ASSERT(rarPlugin->readListLine(line)); + } + + rarPlugin->deleteLater(); +} diff --git a/ark/plugins/clirarplugin/tests/clirartest.h b/ark/plugins/clirarplugin/tests/clirartest.h new file mode 100644 index 00000000..044b8312 --- /dev/null +++ b/ark/plugins/clirarplugin/tests/clirartest.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2011 Raphael Kubo da Costa + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ( INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION ) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef CLIRARTEST_H +#define CLIRARTEST_H + +#include + +class CliRarTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void testReadCorruptedArchive(); + void testParseSymlink(); +}; + +#endif diff --git a/ark/plugins/clirarplugin/tests/data/testReadArchiveWithSymlink.txt b/ark/plugins/clirarplugin/tests/data/testReadArchiveWithSymlink.txt new file mode 100644 index 00000000..e6db584d --- /dev/null +++ b/ark/plugins/clirarplugin/tests/data/testReadArchiveWithSymlink.txt @@ -0,0 +1,22 @@ + +UNRAR 4.20 (iconv) freeware Copyright (c) 1993-2012 Alexander Roshal + +Archive foo.rar + +Pathname/Comment + Size Packed Ratio Date Time Attr CRC Meth Ver + Host OS Solid Old +------------------------------------------------------------------------------- + foo/hello1 + 6 16 266% 12-02-14 23:15 -rw-r--r-- 363A3020 m3b 2.9 + Unix No No + foo/hello2 + 6 6 100% 12-02-14 23:15 lrwxr-xr-x 8731D904 m0b 2.0 + Unix No No + --> hello1 + foo + 0 0 0% 12-02-14 23:15 drwxr-xr-x 00000000 m0 2.0 + Unix No No +------------------------------------------------------------------------------- + 3 12 22 183% + diff --git a/ark/plugins/clirarplugin/tests/data/testReadCorruptedArchive.txt b/ark/plugins/clirarplugin/tests/data/testReadCorruptedArchive.txt new file mode 100644 index 00000000..be702812 --- /dev/null +++ b/ark/plugins/clirarplugin/tests/data/testReadCorruptedArchive.txt @@ -0,0 +1,23 @@ +UNRAR 3.90 beta 3 freeware Copyright (c) 1993-2009 Alexander Roshal + +Volume foo.r45 + +Recovery record is present + +Pathname/Comment + Size Packed Ratio Date Time Attr CRC Meth Ver + Host OS Solid Old +------------------------------------------------------------------------------- + some-file.ext + 732522496 14851208 <-> 29-10-10 20:47 .....A. 0868E5EA m0g 2.0 + Win95/NT No No +Data header type: RR + RR + 148638 148638 100% 00-00-80 00:00 .....B 3622BF05 m0a 2.9 + Win95/NT No No +??? - the file header is corrupt +------------------------------------------------------------------------------- + 0 0 14851208 0% volume 46 + + 1 732522496 732522496 100% + diff --git a/ark/plugins/clizipplugin/CMakeLists.txt b/ark/plugins/clizipplugin/CMakeLists.txt new file mode 100644 index 00000000..5e180fb9 --- /dev/null +++ b/ark/plugins/clizipplugin/CMakeLists.txt @@ -0,0 +1,21 @@ +########### next target ############### + +set(SUPPORTED_CLIZIP_MIMETYPES "application/x-java-archive;application/zip;") + +set(kerfuffle_clizip_SRCS cliplugin.cpp) + +kde4_add_plugin(kerfuffle_clizip ${kerfuffle_clizip_SRCS}) + +target_link_libraries(kerfuffle_clizip ${KDE4_KDECORE_LIBS} ${KDE4_KIO_LIBS} kerfuffle ) + +configure_file( + ${CMAKE_CURRENT_SOURCE_DIR}/kerfuffle_clizip.desktop.cmake + ${CMAKE_CURRENT_BINARY_DIR}/kerfuffle_clizip.desktop +) + +########### install files ############### + +install(TARGETS kerfuffle_clizip DESTINATION ${PLUGIN_INSTALL_DIR} ) +install( FILES ${CMAKE_CURRENT_BINARY_DIR}/kerfuffle_clizip.desktop DESTINATION ${SERVICES_INSTALL_DIR} ) + +set(SUPPORTED_ARK_MIMETYPES "${SUPPORTED_ARK_MIMETYPES}${SUPPORTED_CLIZIP_MIMETYPES}" PARENT_SCOPE) diff --git a/ark/plugins/clizipplugin/cliplugin.cpp b/ark/plugins/clizipplugin/cliplugin.cpp new file mode 100644 index 00000000..1be2a10f --- /dev/null +++ b/ark/plugins/clizipplugin/cliplugin.cpp @@ -0,0 +1,143 @@ +/* + * ark -- archiver for the KDE project + * + * Copyright (C) 2009 Harald Hvaal + * Copyright (C) 2009-2011 Raphael Kubo da Costa + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "cliplugin.h" +#include "kerfuffle/cliinterface.h" +#include "kerfuffle/kerfuffle_export.h" + +#include + +#include +#include +#include +#include +#include +#include + +using namespace Kerfuffle; + +CliPlugin::CliPlugin(QObject *parent, const QVariantList & args) + : CliInterface(parent, args) + , m_status(Header) +{ +} + +CliPlugin::~CliPlugin() +{ +} + +// #208091: infozip applies special meanings to some characters, so we +// need to escape them with backslashes.see match.c in +// infozip's source code +QString CliPlugin::escapeFileName(const QString &fileName) const +{ + const QString escapedCharacters(QLatin1String("[]*?^-\\!")); + + QString quoted; + const int len = fileName.length(); + const QLatin1Char backslash('\\'); + quoted.reserve(len * 2); + + for (int i = 0; i < len; ++i) { + if (escapedCharacters.contains(fileName.at(i))) { + quoted.append(backslash); + } + + quoted.append(fileName.at(i)); + } + + return quoted; +} + +ParameterList CliPlugin::parameterList() const +{ + static ParameterList p; + + if (p.isEmpty()) { + p[CaptureProgress] = false; + p[ListProgram] = QStringList() << QLatin1String( "zipinfo" ); + p[ExtractProgram] = QStringList() << QLatin1String( "unzip" ); + p[DeleteProgram] = p[AddProgram] = QStringList() << QLatin1String( "zip" ); + + p[ListArgs] = QStringList() << QLatin1String( "-l" ) << QLatin1String( "-T" ) << QLatin1String( "$Archive" ); + p[ExtractArgs] = QStringList() << QLatin1String( "$PreservePathSwitch" ) << QLatin1String( "$PasswordSwitch" ) << QLatin1String( "$Archive" ) << QLatin1String( "$Files" ); + p[PreservePathSwitch] = QStringList() << QLatin1String( "" ) << QLatin1String( "-j" ); + p[PasswordSwitch] = QStringList() << QLatin1String( "-P$Password" ); + + p[DeleteArgs] = QStringList() << QLatin1String( "-d" ) << QLatin1String( "$Archive" ) << QLatin1String( "$Files" ); + + p[FileExistsExpression] = QLatin1String( "^replace (.+)\\?" ); + p[FileExistsInput] = QStringList() + << QLatin1String( "y" ) //overwrite + << QLatin1String( "n" ) //skip + << QLatin1String( "A" ) //overwrite all + << QLatin1String( "N" ) //autoskip + ; + + p[AddArgs] = QStringList() << QLatin1String( "-r" ) << QLatin1String( "$Archive" ) << QLatin1String( "$Files" ); + + p[PasswordPromptPattern] = QLatin1String(" password: "); + p[WrongPasswordPatterns] = QStringList() << QLatin1String( "incorrect password" ); + //p[ExtractionFailedPatterns] = QStringList() << "CRC failed"; + } + return p; +} + +bool CliPlugin::readListLine(const QString &line) +{ + static const QRegExp entryPattern(QLatin1String( + "^(\\S+)\\s+(\\S+)\\s+(\\S+)\\s+(\\S+)\\s+(\\S+)\\s+(\\S+)\\s+(\\S+)\\s+(\\d{8}).(\\d{6})\\s+(.+)$") ); + + switch (m_status) { + case Header: + m_status = Entry; + break; + case Entry: + if (entryPattern.indexIn(line) != -1) { + ArchiveEntry e; + e[Permissions] = entryPattern.cap(1); + + // #280354: infozip may not show the right attributes for a given directory, so an entry + // ending with '/' is actually more reliable than 'd' bein in the attributes. + e[IsDirectory] = entryPattern.cap(10).endsWith(QLatin1Char('/')); + + e[Size] = entryPattern.cap(4).toInt(); + QString status = entryPattern.cap(5); + if (status[0].isUpper()) { + e[IsPasswordProtected] = true; + } + e[CompressedSize] = entryPattern.cap(6).toInt(); + + const QDateTime ts(QDate::fromString(entryPattern.cap(8), QLatin1String( "yyyyMMdd" )), + QTime::fromString(entryPattern.cap(9), QLatin1String( "hhmmss" ))); + e[Timestamp] = ts; + + e[FileName] = e[InternalID] = entryPattern.cap(10); + emit entry(e); + } + break; + } + + return true; +} + +KERFUFFLE_EXPORT_PLUGIN(CliPlugin) + diff --git a/ark/plugins/clizipplugin/cliplugin.h b/ark/plugins/clizipplugin/cliplugin.h new file mode 100644 index 00000000..3a310e0a --- /dev/null +++ b/ark/plugins/clizipplugin/cliplugin.h @@ -0,0 +1,47 @@ +/* + * ark -- archiver for the KDE project + * + * Copyright (C) 2011 Raphael Kubo da Costa + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef CLIPLUGIN_H +#define CLIPLUGIN_H + +#include "kerfuffle/cliinterface.h" + +class CliPlugin : public Kerfuffle::CliInterface +{ + Q_OBJECT + +public: + explicit CliPlugin(QObject *parent, const QVariantList &args); + virtual ~CliPlugin(); + + virtual QString escapeFileName(const QString &fileName) const; + + virtual Kerfuffle::ParameterList parameterList() const; + + virtual bool readListLine(const QString &line); + +private: + enum { + Header = 0, + Entry + } m_status; +}; + +#endif // CLIPLUGIN_H diff --git a/ark/plugins/clizipplugin/kerfuffle_clizip.desktop.cmake b/ark/plugins/clizipplugin/kerfuffle_clizip.desktop.cmake new file mode 100644 index 00000000..32ceced7 --- /dev/null +++ b/ark/plugins/clizipplugin/kerfuffle_clizip.desktop.cmake @@ -0,0 +1,69 @@ +[Desktop Entry] +Type=Service +X-KDE-ServiceTypes=Kerfuffle/Plugin +X-KDE-Library=kerfuffle_clizip +X-KDE-PluginInfo-Author=Harald Hvaal +X-KDE-PluginInfo-Email=haraldhv@stud.ntnu.no +X-KDE-PluginInfo-Name=kerfuffle_clizip +X-KDE-PluginInfo-Version=0.0.1 +X-KDE-PluginInfo-Website=http://www.kde.org +X-KDE-PluginInfo-License=GPLv2+ +X-KDE-Priority=160 +X-KDE-Kerfuffle-APIRevision=1 +X-KDE-Kerfuffle-ReadWrite=true +Name=ZIP archive plugin +Name[ar]=ملحق أرشيف ZIP +Name[ast]=Complementu d'archivu comprimíu ZIP +Name[bg]=Приставка за архиви ZIP +Name[bs]=Priključak ZIP arhiva +Name[ca]=Connector per arxius ZIP +Name[ca@valencia]=Connector per arxius ZIP +Name[cs]=Modul pro archiv ZIP +Name[da]=Plugin til ZIP-arkiver +Name[de]=ZIP-Archiv-Modul +Name[el]=Πρόσθετο αρχειοθήκης ZIP +Name[en_GB]=ZIP archive plugin +Name[es]=Complemento de archivo comprimido ZIP +Name[et]=ZIP-arhiivi plugin +Name[eu]=ZIP artxiboen plugina +Name[fi]=Zip-pakkaustuki +Name[fr]=Module externe d'archive « ZIP » +Name[ga]=Breiseán cartlainne ZIP +Name[gl]=Extensión de arquivo ZIP +Name[hr]=Arhivni priključak ZIP +Name[hu]=ZIP modul +Name[ia]=Plugin de archivar zip +Name[id]=Pengaya arsip ZIP +Name[it]=estensione per archivi ZIP +Name[ja]=ZIP アーカイブ用プラグイン +Name[kk]=ZIP архив плагині +Name[km]=កម្មវិធី​ជំនួយ​ប័ណ្ណសារ ZIP +Name[ko]=ZIP 압축 플러그인 +Name[lt]=ZIP archyvo priedas +Name[lv]=ZIP arhīvu spraudnis +Name[mr]=ZIP संग्रह प्लगइन +Name[nb]=Programtillegg for ZIP-arkiv +Name[nds]=Zip-Archievmoduul +Name[nl]=ZIP-archiefplug-in +Name[nn]=ZIP-arkivtillegg +Name[pa]=ਜ਼ਿੱਪ ਅਕਾਇਵ ਪਲੱਗਇਨ +Name[pl]=Wtyczka archiwów ZIP +Name[pt]='Plugin' de pacotes ZIP +Name[pt_BR]=Plugin de arquivos ZIP +Name[ro]=Modul de arhivă ZIP +Name[ru]=Поддержка архивов ZIP +Name[sk]=Modul ZIP archívu +Name[sl]=Vstavek za arhive ZIP +Name[sq]=ZIP arkiv plugin +Name[sr]=Прикључак ЗИП архива +Name[sr@ijekavian]=Прикључак ЗИП архива +Name[sr@ijekavianlatin]=Priključak ZIP arhiva +Name[sr@latin]=Priključak ZIP arhiva +Name[sv]=Insticksprogram för ZIP-arkiv +Name[th]=ส่วนเสริมการจัดการแฟ้มจัดเก็บบีบอัดแบบ ZIP +Name[tr]=ZIP arşivi eklentisi +Name[uk]=Додаток для архівів ZIP +Name[x-test]=xxZIP archive pluginxx +Name[zh_CN]=ZIP 归档插件 +Name[zh_TW]=ZIP 壓縮檔外掛程式 +MimeType=@SUPPORTED_CLIZIP_MIMETYPES@ diff --git a/ark/plugins/karchiveplugin/CMakeLists.txt b/ark/plugins/karchiveplugin/CMakeLists.txt new file mode 100644 index 00000000..8ef4382e --- /dev/null +++ b/ark/plugins/karchiveplugin/CMakeLists.txt @@ -0,0 +1,21 @@ +########### next target ############### + +set(SUPPORTED_KARCHIVE_MIMETYPES "application/zip;application/x-zip-compressed;application/x-tar;application/x-compressed-tar;application/x-bzip-compressed-tar;") + +set(kerfuffle_karchive_SRCS karchiveplugin.cpp) + +kde4_add_plugin(kerfuffle_karchive ${kerfuffle_karchive_SRCS}) + +target_link_libraries(kerfuffle_karchive ${KDE4_KDECORE_LIBS} ${KDE4_KIO_LIBS} kerfuffle ) + +configure_file( + ${CMAKE_CURRENT_SOURCE_DIR}/kerfuffle_karchive.desktop.cmake + ${CMAKE_CURRENT_BINARY_DIR}/kerfuffle_karchive.desktop +) + +########### install files ############### + +install(TARGETS kerfuffle_karchive DESTINATION ${PLUGIN_INSTALL_DIR} ) +install( FILES kerfuffle_karchive.desktop DESTINATION ${SERVICES_INSTALL_DIR} ) + +set(SUPPORTED_ARK_MIMETYPES "${SUPPORTED_ARK_MIMETYPES}${SUPPORTED_KARCHIVE_MIMETYPES}" PARENT_SCOPE) diff --git a/ark/plugins/karchiveplugin/karchiveplugin.cpp b/ark/plugins/karchiveplugin/karchiveplugin.cpp new file mode 100644 index 00000000..31ce1d89 --- /dev/null +++ b/ark/plugins/karchiveplugin/karchiveplugin.cpp @@ -0,0 +1,331 @@ +/* + * ark -- archiver for the KDE project + * + * Copyright (C) 2007 Henrique Pinto + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ +#include "karchiveplugin.h" +#include "kerfuffle/queries.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +KArchiveInterface::KArchiveInterface(QObject *parent, const QVariantList &args) + : ReadWriteArchiveInterface(parent, args), m_archive(0) +{ + kDebug(); +} + +KArchiveInterface::~KArchiveInterface() +{ + delete m_archive; + m_archive = 0; +} + +KArchive *KArchiveInterface::archive() +{ + if (m_archive == 0) { + KMimeType::Ptr mimeType = KMimeType::findByPath(filename()); + + if (mimeType->is(QLatin1String("application/zip"))) { + m_archive = new KZip(filename()); + } else { + m_archive = new KTar(filename()); + } + + } + return m_archive; +} + +bool KArchiveInterface::list() +{ + kDebug(); + if (!archive()->isOpen() && !archive()->open(QIODevice::ReadOnly)) { + emit error(i18nc("@info", "Could not open the archive %1 for reading", filename())); + return false; + } else { + return browseArchive(archive()); + } +} + +void KArchiveInterface::getAllEntries(const KArchiveDirectory *dir, const QString &prefix, QList< QVariant > &list) +{ + foreach(const QString &entryName, dir->entries()) { + const KArchiveEntry *entry = dir->entry(entryName); + if (entry->isDirectory()) { + QString newPrefix = (prefix.isEmpty() ? prefix : prefix + QLatin1Char('/')) + entryName; + getAllEntries(static_cast(entry), newPrefix, list); + } + else { + list.append(prefix + QLatin1Char('/') + entryName); + } + } +} + +bool KArchiveInterface::copyFiles(const QList &files, const QString &destinationDirectory, ExtractionOptions options) +{ + const bool preservePaths = options.value(QLatin1String("PreservePaths")).toBool(); + const KArchiveDirectory *dir = archive()->directory(); + + if (!archive()->isOpen() && !archive()->open(QIODevice::ReadOnly)) { + emit error(i18nc("@info", "Could not open the archive %1 for reading", filename())); + return false; + } + + QList extrFiles = files; + if (extrFiles.isEmpty()) { // All files should be extracted + getAllEntries(dir, QString(), extrFiles); + } + + bool overwriteAllSelected = false; + bool autoSkipSelected = false; + QSet dirCache; + foreach(const QVariant &file, extrFiles) { + QString realDestination = destinationDirectory; + const KArchiveEntry *archiveEntry = dir->entry(file.toString()); + if (!archiveEntry) { + emit error(i18nc("@info", "File %1 not found in the archive" , file.toString())); + return false; + } + + if (preservePaths) { + QFileInfo fi(file.toString()); + QDir dest(destinationDirectory); + QString filepath = archiveEntry->isDirectory() ? fi.filePath() : fi.path(); + if (!dirCache.contains(filepath)) { + if (!dest.mkpath(filepath)) { + emit error(i18nc("@info", "Error creating directory %1", filepath)); + return false; + } + dirCache << filepath; + } + realDestination = dest.absolutePath() + QLatin1Char('/') + filepath; + } + + // TODO: handle errors, copyTo fails silently + if (!archiveEntry->isDirectory()) { // We don't need to do anything about directories + if (QFile::exists(realDestination + QLatin1Char('/') + archiveEntry->name()) && !overwriteAllSelected) { + if (autoSkipSelected) { + continue; + } + + int response = handleFileExistsMessage(realDestination, archiveEntry->name()); + + if (response == OverwriteCancel) { + break; + } + if (response == OverwriteYes || response == OverwriteAll) { + static_cast(archiveEntry)->copyTo(realDestination); + if (response == OverwriteAll) { + overwriteAllSelected = true; + } + } + if (response == OverwriteAutoSkip) { + autoSkipSelected = true; + } + } + else { + static_cast(archiveEntry)->copyTo(realDestination); + } + } + } + + return true; +} + +int KArchiveInterface::handleFileExistsMessage(const QString &dir, const QString &fileName) +{ + Kerfuffle::OverwriteQuery query(dir + QLatin1Char('/') + fileName); + query.setNoRenameMode(true); + emit userQuery(&query); + query.waitForResponse(); + + if (query.responseOverwrite()) { + return OverwriteYes; + } else if (query.responseSkip()) { + return OverwriteSkip; + } else if (query.responseOverwriteAll()) { + return OverwriteAll; + } else if (query.responseAutoSkip()) { + return OverwriteAutoSkip; + } + + return OverwriteCancel; +} + +bool KArchiveInterface::browseArchive(KArchive *archive) +{ + return processDir(archive->directory()); +} + +bool KArchiveInterface::processDir(const KArchiveDirectory *dir, const QString & prefix) +{ + foreach(const QString& entryName, dir->entries()) { + const KArchiveEntry *entry = dir->entry(entryName); + createEntryFor(entry, prefix); + if (entry->isDirectory()) { + QString newPrefix = (prefix.isEmpty() ? prefix : prefix + QLatin1Char('/')) + entryName; + processDir(static_cast(entry), newPrefix); + } + } + return true; +} + +void KArchiveInterface::createEntryFor(const KArchiveEntry *aentry, const QString& prefix) +{ + ArchiveEntry e; + QString fileName = prefix.isEmpty() ? aentry->name() : prefix + QLatin1Char('/') + aentry->name(); + + if (aentry->isDirectory() && !fileName.endsWith(QLatin1Char('/'))) + fileName += QLatin1Char('/'); + + e[ FileName ] = fileName; + e[ InternalID ] = e[ FileName ]; + e[ Permissions ] = permissionsString(aentry->permissions()); + e[ Owner ] = aentry->user(); + e[ Group ] = aentry->group(); + e[ IsDirectory ] = aentry->isDirectory(); + e[ Timestamp ] = aentry->datetime(); + if (!aentry->symLinkTarget().isEmpty()) { + e[ Link ] = aentry->symLinkTarget(); + } + if (aentry->isFile()) { + e[ Size ] = static_cast(aentry)->size(); + } + else { + e[ Size ] = 0; + } + emit entry(e); +} + +bool KArchiveInterface::addFiles(const QStringList &files, const Kerfuffle::CompressionOptions &options) +{ + Q_UNUSED(options) + kDebug() << "Starting..."; +// delete m_archive; +// m_archive = 0; + if (archive()->isOpen()) { + archive()->close(); + } + if (!archive()->open(QIODevice::ReadWrite)) { + emit error(i18nc("@info", "Could not open the archive %1 for writing.", filename())); + return false; + } + + kDebug() << "Archive opened for writing..."; + kDebug() << "Will add " << files.count() << " files"; + foreach(const QString &path, files) { + kDebug() << "Adding " << path; + QFileInfo fi(path); + Q_ASSERT(fi.exists()); + + if (fi.isDir()) { + if (archive()->addLocalDirectory(path, fi.fileName())) { + const KArchiveEntry *entry = archive()->directory()->entry(fi.fileName()); + createEntryFor(entry, QString()); + processDir((KArchiveDirectory*) archive()->directory()->entry(fi.fileName()), fi.fileName()); + } else { + emit error(i18nc("@info", "Could not add the directory %1 to the archive", path)); + return false; + } + } else { + if (archive()->addLocalFile(path, fi.fileName())) { + const KArchiveEntry *entry = archive()->directory()->entry(fi.fileName()); + createEntryFor(entry, QString()); + } else { + emit error(i18nc("@info", "Could not add the file %1 to the archive.", path)); + return false; + } + } + } + kDebug() << "Closing the archive"; + archive()->close(); + kDebug() << "Done"; + return true; +} + +bool KArchiveInterface::deleteFiles(const QList & files) +{ + Q_UNUSED(files) + return false; +} + +// Borrowed and adapted from KFileItemPrivate::parsePermissions. +QString KArchiveInterface::permissionsString(mode_t perm) +{ + static char buffer[ 12 ]; + + char uxbit,gxbit,oxbit; + + if ( (perm & (S_IXUSR|S_ISUID)) == (S_IXUSR|S_ISUID) ) + uxbit = 's'; + else if ( (perm & (S_IXUSR|S_ISUID)) == S_ISUID ) + uxbit = 'S'; + else if ( (perm & (S_IXUSR|S_ISUID)) == S_IXUSR ) + uxbit = 'x'; + else + uxbit = '-'; + + if ( (perm & (S_IXGRP|S_ISGID)) == (S_IXGRP|S_ISGID) ) + gxbit = 's'; + else if ( (perm & (S_IXGRP|S_ISGID)) == S_ISGID ) + gxbit = 'S'; + else if ( (perm & (S_IXGRP|S_ISGID)) == S_IXGRP ) + gxbit = 'x'; + else + gxbit = '-'; + + if ( (perm & (S_IXOTH|S_ISVTX)) == (S_IXOTH|S_ISVTX) ) + oxbit = 't'; + else if ( (perm & (S_IXOTH|S_ISVTX)) == S_ISVTX ) + oxbit = 'T'; + else if ( (perm & (S_IXOTH|S_ISVTX)) == S_IXOTH ) + oxbit = 'x'; + else + oxbit = '-'; + + // Include the type in the first char like kde3 did; people are more used to seeing it, + // even though it's not really part of the permissions per se. + if (S_ISDIR(perm)) + buffer[0] = 'd'; + else if (S_ISLNK(perm)) + buffer[0] = 'l'; + else + buffer[0] = '-'; + + buffer[1] = ((( perm & S_IRUSR ) == S_IRUSR ) ? 'r' : '-' ); + buffer[2] = ((( perm & S_IWUSR ) == S_IWUSR ) ? 'w' : '-' ); + buffer[3] = uxbit; + buffer[4] = ((( perm & S_IRGRP ) == S_IRGRP ) ? 'r' : '-' ); + buffer[5] = ((( perm & S_IWGRP ) == S_IWGRP ) ? 'w' : '-' ); + buffer[6] = gxbit; + buffer[7] = ((( perm & S_IROTH ) == S_IROTH ) ? 'r' : '-' ); + buffer[8] = ((( perm & S_IWOTH ) == S_IWOTH ) ? 'w' : '-' ); + buffer[9] = oxbit; + buffer[10] = 0; + + return QString::fromLatin1(buffer); +} + +KERFUFFLE_EXPORT_PLUGIN(KArchiveInterface) diff --git a/ark/plugins/karchiveplugin/karchiveplugin.h b/ark/plugins/karchiveplugin/karchiveplugin.h new file mode 100644 index 00000000..db7a6f51 --- /dev/null +++ b/ark/plugins/karchiveplugin/karchiveplugin.h @@ -0,0 +1,70 @@ +/* + * ark -- archiver for the KDE project + * + * Copyright (C) 2007 Henrique Pinto + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ +#ifndef KARCHIVEPLUGIN_H +#define KARCHIVEPLUGIN_H +#include "kerfuffle/archiveinterface.h" + +using namespace Kerfuffle; + +class KArchive; +class KArchiveEntry; +class KArchiveDirectory; + +class KArchiveInterface: public ReadWriteArchiveInterface +{ + Q_OBJECT +public: + explicit KArchiveInterface(QObject *parent = 0, const QVariantList &args = QVariantList()); + ~KArchiveInterface(); + + bool list(); + bool copyFiles(const QList &files, const QString &destinationDirectory, ExtractionOptions options); + + bool addFiles(const QStringList &files, const CompressionOptions &options); + bool deleteFiles(const QList & files); + +private: + enum { + OverwriteYes, + OverwriteSkip, + OverwriteAll, + OverwriteAutoSkip, + OverwriteCancel + }; + + bool browseArchive(KArchive *archive); + + bool processDir(const KArchiveDirectory *dir, const QString & prefix = QString()); + + void createEntryFor(const KArchiveEntry *aentry, const QString& prefix); + + QString permissionsString(mode_t perm); + + void getAllEntries(const KArchiveDirectory *dir, const QString &prefix, QList< QVariant > &list); + + int handleFileExistsMessage(const QString &dir, const QString &fileName); + + KArchive *archive(); + + KArchive *m_archive; +}; + +#endif // KARCHIVEPLUGIN_H diff --git a/ark/plugins/karchiveplugin/kerfuffle_karchive.desktop.cmake b/ark/plugins/karchiveplugin/kerfuffle_karchive.desktop.cmake new file mode 100644 index 00000000..84383585 --- /dev/null +++ b/ark/plugins/karchiveplugin/kerfuffle_karchive.desktop.cmake @@ -0,0 +1,129 @@ +[Desktop Entry] +Type=Service +X-KDE-ServiceTypes=Kerfuffle/Plugin +X-KDE-Library=kerfuffle_karchive +X-KDE-PluginInfo-Author=Henrique Pinto +X-KDE-PluginInfo-Email=henrique.pinto@kdemail.net +X-KDE-PluginInfo-Name=kerfuffle_karchive +X-KDE-PluginInfo-Version=0.0.1 +X-KDE-PluginInfo-Website=http://www.kde.org +X-KDE-PluginInfo-License=GPLv2+ +X-KDE-Priority=117 +X-KDE-Kerfuffle-APIRevision=1 +Name=kerfuffle_karchive +Name[ar]=kerfuffle_karchive +Name[ast]=kerfuffle_karchive +Name[bg]=kerfuffle_karchive +Name[bs]=kerfuffle_karchive +Name[ca]=kerfuffle_karchive +Name[ca@valencia]=kerfuffle_karchive +Name[cs]=kerfuffle_karchive +Name[da]=kerfuffle_karchive +Name[de]=kerfuffle_karchive +Name[el]=kerfuffle_karchive +Name[en_GB]=kerfuffle_karchive +Name[es]=kerfuffle_karchive +Name[et]=kerfuffle_karchive +Name[eu]=kerfuffle_karchive +Name[fi]=kerfuffle_karchive +Name[fr]=kerfuffle_karchive +Name[ga]=kerfuffle_karchive +Name[gl]=kerfuffle_karchive +Name[he]=kerfuffle_karchive +Name[hne]=करफुफल_केआर्काइव +Name[hr]=kerfuffle_karchive +Name[hu]=kerfuffle_karchive +Name[ia]=kerfuffle_karchive +Name[id]=kerfuffle_karchive +Name[it]=kerfuffle_karchive +Name[ja]=kerfuffle_karchive +Name[kk]=kerfuffle_karchive +Name[km]=kerfuffle_karchive +Name[ko]=kerfuffle_karchive +Name[lt]=kerfuffle_karchive +Name[lv]=kerfuffle_karchive +Name[mr]=कर्फफल_के-संग्रह +Name[nb]=kerfuffle_karchive +Name[nds]=kerfuffle_karchive +Name[nl]=kerfuffle_karchive +Name[nn]=kerfuffle_karchive +Name[pa]=kerfuffle_karchive +Name[pl]=kerfuffle_karchive +Name[pt]=kerfuffle_karchive +Name[pt_BR]=kerfuffle_karchive +Name[ro]=kerfuffle_karchive +Name[ru]=kerfuffle_karchive +Name[sk]=kerfuffle_karchive +Name[sl]=kerfuffle_karchive +Name[sq]=kerfuffle_karchive +Name[sr]=kerfuffle_karchive +Name[sr@ijekavian]=kerfuffle_karchive +Name[sr@ijekavianlatin]=kerfuffle_karchive +Name[sr@latin]=kerfuffle_karchive +Name[sv]=Kerfuffle Karchive +Name[ta]=கெர்ஃபஃபில் கேஆர்கைவ் +Name[th]=kerfuffle_karchive +Name[tr]=kerfuffle_karchive +Name[uk]=kerfuffle_karchive +Name[wa]=kerfuffle_karchive +Name[x-test]=xxkerfuffle_karchivexx +Name[zh_CN]=kerfuffle_karchive +Name[zh_TW]=kerfuffle_karchive +Comment=KArchive plugin for Kerfuffle +Comment[ar]=KArchive ملحق لـ Kerfuffle +Comment[ast]=Aplicación KArchive pa Kerfuffle +Comment[bg]=Приставка KArchive за Kerfuffle +Comment[bs]=Priključak KArchive za Kerfafl +Comment[ca]=Connector del KArchive pel Kerfuffle +Comment[ca@valencia]=Connector del KArchive pel Kerfuffle +Comment[cs]=KArchive modul pro Kerfuffle +Comment[da]=KArchive-plugin til Kerfuffle +Comment[de]=KArchive-Modul für Kerfuffle +Comment[el]=πρόσθετο KArchive για τη Kerfuffle +Comment[en_GB]=KArchive plugin for Kerfuffle +Comment[es]=Complemento KArchive para Kerfuffle +Comment[et]=Kerfuffle'i KArchive plugin +Comment[eu]=Kerfuffle-rentzako KArchive plugina +Comment[fi]=KArchive-pakkaustuki +Comment[fr]=Module externe « KArchive » pour Kerfuffle +Comment[ga]=Breiseán KArchive le haghaidh Kerfuffle +Comment[gl]=Extensión de KArchive para Kerfuffle +Comment[he]=תוסף KArchive עבור Kerfuffle +Comment[hne]=करफुफल बर केआर्काइव प्लगइन +Comment[hr]=Priključak KArchive za Kerfuffle +Comment[hu]=KArchive Kerfuffle-modul +Comment[ia]=Plugin de Karchive per Kerfuffle +Comment[id]=Pengaya KArchive untuk Kerfuffle +Comment[it]=Estensione KArchive per Kerfuffle +Comment[ja]=Kerfuffle のための KArchive プラグイン +Comment[kk]=Kerfuffle-ге арналған KArchive плагині +Comment[km]=កម្មវិធី​របស់ KArchive សម្រាប់ Kerfuffle +Comment[ko]=Kerfuffle을 위한 KArchive 플러그인 +Comment[lt]=KArchive Kerfuffle priedas +Comment[lv]=Kerfuffle KArchive spraudnis +Comment[mr]=कर्फफल करिता के-संग्रह प्लगइन +Comment[nb]=KArchive programtillegg for Kerfuffle +Comment[nds]=KArchive-Moduul för Kerfuffle +Comment[nl]=KArchive-plug-in voor Kerfuffle +Comment[nn]=KArchive-programtillegg til Kerfuffle +Comment[pl]=Wtyczka KArchive dla Kerfuffle +Comment[pt]='Plugin' do KArchive para o Kerfuffle +Comment[pt_BR]=Plugin KArchive para a Kerfuffle +Comment[ro]=Modul KArchive pentru Kerfuffle +Comment[ru]=Модуль KArchive для Kerfuffle +Comment[sk]=KArchive modul pre Kerfuffle +Comment[sl]=Vstavek KArchive za Kerfuffle +Comment[sq]=KArchive plugin për Kerfuffle +Comment[sr]=Прикључак KArchive за Керфафл +Comment[sr@ijekavian]=Прикључак KArchive за Керфафл +Comment[sr@ijekavianlatin]=Priključak KArchive za Kerfuffle +Comment[sr@latin]=Priključak KArchive za Kerfuffle +Comment[sv]=Karchive-insticksprogram för Kerfuffle +Comment[ta]=கெர்ஃபஃபில் க்கு கேஆர்கைவ் சொருகி +Comment[th]=ส่วนเสริม Kerfuffle สำหรับใช้งานร่วมกับ KArchive +Comment[tr]=Kerfuffle için KArchive eklentisi +Comment[uk]=Додаток KArchive для Kerfuffle +Comment[x-test]=xxKArchive plugin for Kerfufflexx +Comment[zh_CN]=Kerfuffle 的 KArchive 插件 +Comment[zh_TW]=Kerfuffle 的 KArchive 外掛程式 +MimeType=@SUPPORTED_KARCHIVE_MIMETYPES@ diff --git a/ark/plugins/libarchive/CMakeLists.txt b/ark/plugins/libarchive/CMakeLists.txt new file mode 100644 index 00000000..5d6d0096 --- /dev/null +++ b/ark/plugins/libarchive/CMakeLists.txt @@ -0,0 +1,41 @@ +include_directories(${LIBARCHIVE_INCLUDE_DIR}) + +########### next target ############### +set(SUPPORTED_LIBARCHIVE_READONLY_MIMETYPES "application/x-deb;application/x-cd-image;application/x-bcpio;application/x-cpio;application/x-cpio-compressed;application/x-sv4cpio;application/x-sv4crc;") +set(SUPPORTED_LIBARCHIVE_READWRITE_MIMETYPES "application/x-tar;application/x-compressed-tar;application/x-bzip-compressed-tar;application/x-tarz;application/x-xz-compressed-tar;application/x-lzma-compressed-tar;") +if(HAVE_LIBARCHIVE_RPM_SUPPORT) + set(SUPPORTED_LIBARCHIVE_READONLY_MIMETYPES "${SUPPORTED_LIBARCHIVE_READONLY_MIMETYPES}application/x-rpm;application/x-source-rpm;") +endif(HAVE_LIBARCHIVE_RPM_SUPPORT) +if(HAVE_LIBARCHIVE_CAB_SUPPORT) + set(SUPPORTED_LIBARCHIVE_READONLY_MIMETYPES "${SUPPORTED_LIBARCHIVE_READONLY_MIMETYPES}application/vnd.ms-cab-compressed;") +endif(HAVE_LIBARCHIVE_CAB_SUPPORT) + +# This MIME type was originally set in ark.desktop but is not mentioned anywhere else. +# Assuming that, if it were supported, it would be here. +set(SUPPORTED_LIBARCHIVE_READONLY_MIMETYPES "${SUPPORTED_LIBARCHIVE_READONLY_MIMETYPES}application/x-servicepack;") + +configure_file( + ${CMAKE_CURRENT_SOURCE_DIR}/kerfuffle_libarchive_readonly.desktop.cmake + ${CMAKE_CURRENT_BINARY_DIR}/kerfuffle_libarchive_readonly.desktop +) + +configure_file( + ${CMAKE_CURRENT_SOURCE_DIR}/kerfuffle_libarchive.desktop.cmake + ${CMAKE_CURRENT_BINARY_DIR}/kerfuffle_libarchive.desktop +) + +set(kerfuffle_libarchive_SRCS libarchivehandler.cpp) + +kde4_add_plugin(kerfuffle_libarchive ${kerfuffle_libarchive_SRCS}) + +target_link_libraries(kerfuffle_libarchive ${KDE4_KIO_LIBS} ${KDE4_KDECORE_LIBS} ${LIBARCHIVE_LIBRARY} kerfuffle ) + +install(TARGETS kerfuffle_libarchive DESTINATION ${PLUGIN_INSTALL_DIR} ) + + +########### install files ############### + +install( FILES ${CMAKE_CURRENT_BINARY_DIR}/kerfuffle_libarchive.desktop DESTINATION ${SERVICES_INSTALL_DIR} ) +install( FILES ${CMAKE_CURRENT_BINARY_DIR}/kerfuffle_libarchive_readonly.desktop DESTINATION ${SERVICES_INSTALL_DIR} ) + +set(SUPPORTED_ARK_MIMETYPES "${SUPPORTED_ARK_MIMETYPES}${SUPPORTED_LIBARCHIVE_READWRITE_MIMETYPES}${SUPPORTED_LIBARCHIVE_READONLY_MIMETYPES}" PARENT_SCOPE) diff --git a/ark/plugins/libarchive/kerfuffle_libarchive.desktop.cmake b/ark/plugins/libarchive/kerfuffle_libarchive.desktop.cmake new file mode 100644 index 00000000..5a943810 --- /dev/null +++ b/ark/plugins/libarchive/kerfuffle_libarchive.desktop.cmake @@ -0,0 +1,130 @@ +[Desktop Entry] +Type=Service +X-KDE-ServiceTypes=Kerfuffle/Plugin +X-KDE-Library=kerfuffle_libarchive +X-KDE-PluginInfo-Author=Henrique Pinto +X-KDE-PluginInfo-Email=henrique.pinto@kdemail.net +X-KDE-PluginInfo-Name=kerfuffle_libarchive +X-KDE-PluginInfo-Version=0.0.1 +X-KDE-PluginInfo-Website=http://www.kde.org +X-KDE-PluginInfo-License=BSD +X-KDE-Priority=100 +X-KDE-Kerfuffle-APIRevision=1 +X-KDE-Kerfuffle-ReadWrite=true +Name=kerfuffle_libarchive +Name[ar]=kerfuffle_libarchive +Name[ast]=kerfuffle_libarchive +Name[bg]=kerfuffle_libarchive +Name[bs]=kerfuffle_libarchive +Name[ca]=kerfuffle_libarchive +Name[ca@valencia]=kerfuffle_libarchive +Name[cs]=kerfuffle_libarchive +Name[da]=kerfuffle_libarchive +Name[de]=kerfuffle_libarchive +Name[el]=kerfuffle_libarchive +Name[en_GB]=kerfuffle_libarchive +Name[es]=kerfuffle_libarchive +Name[et]=kerfuffle_libarchive +Name[eu]=kerfuffle_libarchive +Name[fi]=kerfuffle_libarchive +Name[fr]=kerfuffle_libarchive +Name[ga]=kerfuffle_libarchive +Name[gl]=kerfuffle_libarchive +Name[he]=kerfuffle_libarchive +Name[hne]=करफुफल_लिबआर्काइव +Name[hr]=kerfuffle_libarchive +Name[hu]=kerfuffle_libarchive +Name[ia]=kerfuffle_libarchive +Name[id]=kerfuffle_libarchive +Name[it]=kerfuffle_libarchive +Name[ja]=kerfuffle_libarchive +Name[kk]=kerfuffle_libarchive +Name[km]=kerfuffle_libarchive +Name[ko]=kerfuffle_libarchive +Name[lt]=kerfuffle_libarchive +Name[lv]=kerfuffle_libarchive +Name[mr]=कर्फफल_लिब-संग्रह +Name[nb]=kerfuffle_libarchive +Name[nds]=kerfuffle_libarchive +Name[nl]=kerfuffle_libarchive +Name[nn]=kerfuffle_libarchive +Name[pa]=kerfuffle_libarchive +Name[pl]=kerfuffle_libarchive +Name[pt]=kerfuffle_libarchive +Name[pt_BR]=kerfuffle_libarchive +Name[ro]=kerfuffle_libarchive +Name[ru]=kerfuffle_libarchive +Name[sk]=kerfuffle_libarchive +Name[sl]=kerfuffle_libarchive +Name[sq]=kerfuffle_libarchive +Name[sr]=kerfuffle_libarchive +Name[sr@ijekavian]=kerfuffle_libarchive +Name[sr@ijekavianlatin]=kerfuffle_libarchive +Name[sr@latin]=kerfuffle_libarchive +Name[sv]=Kerfuffle Libarchive +Name[ta]=கெர்ஃபஃபில் லிப்ஆர்கைவ் +Name[th]=kerfuffle_libarchive +Name[tr]=kerfuffle_libarchive +Name[uk]=kerfuffle_libarchive +Name[wa]=kerfuffle_libarchive +Name[x-test]=xxkerfuffle_libarchivexx +Name[zh_CN]=kerfuffle_libarchive +Name[zh_TW]=kerfuffle_libarchive +Comment=LibArchive Plugin for Kerfuffle +Comment[ar]=LibArchive ملحق لـ Kerfuffle +Comment[ast]=Aplicación LibArchive pa Kerfuffle +Comment[bg]=Приставка LibArchive за Kerfuffle +Comment[bs]=Priključak libarchive za Kerfafl +Comment[ca]=Connector del LibArchive pel Kerfuffle +Comment[ca@valencia]=Connector del LibArchive pel Kerfuffle +Comment[cs]=LibArchive modul pro Kerfuffle +Comment[da]=LibArchive-plugin til Kerfuffle +Comment[de]=LibArchive-Modul für Kerfuffle +Comment[el]=πρόσθετο LibArchive για τη Kerfuffle +Comment[en_GB]=LibArchive Plugin for Kerfuffle +Comment[es]=Complemento LibArchive para Kerfuffle +Comment[et]=Kerfuffle'i LibArchive plugin +Comment[eu]=Kerfuffle-rentzako LibArchive plugina +Comment[fi]=LibArchive-pakkaustuki +Comment[fr]=Module externe « LibArchive » pour Kerfuffle +Comment[ga]=Breiseán LibArchive le haghaidh Kerfuffle +Comment[gl]=Extensión de libarchive para Kerfuffle +Comment[he]=תוסף LibArchive עבור Kerfuffle +Comment[hne]=करफुफल बर लिबआर्काइव प्लगइन +Comment[hr]=Priključak LibArchive za Kerfuffle +Comment[hu]=LibArchive Kerfuffle-modul +Comment[ia]=Plugin de Libarchive per Kerfuffle +Comment[id]=Pengaya LibArchive untuk Kerfuffle +Comment[it]=Estensione LibArchive per Kerfuffle +Comment[ja]=Kerfuffle のための LibArchive プラグイン +Comment[kk]=Kerfuffle-ге арналған LibArchive плагині +Comment[km]=កម្មវិធី​ជំនួយ LibArchive សម្រាប់ Kerfuffle +Comment[ko]=Kerfuffle을 위한 LibArchive 플러그인 +Comment[lt]=LibArchive Kerfuffle priedas +Comment[lv]=Kerfuffle LibArchive spraudnis +Comment[mr]=कर्फफल करिता लिब-संग्रह प्लगइन +Comment[nb]=LibArchive programtillegg for Kerfuffle +Comment[nds]=LibArchive-Moduul för Kerfuffle +Comment[nl]=LibArchive-plug-in voor Kerfuffle +Comment[nn]=LibArchive-programtillegg til Kerfuffle +Comment[pl]=Wtyczka libArchive dla Kerfuffle +Comment[pt]='Plugin' de LibArchive para o Kerfuffle +Comment[pt_BR]=Plugin LibArchive para a Kerfuffle +Comment[ro]=Modul LibArchive pentru Kerfuffle +Comment[ru]=Модуль LibArchive для Kerfuffle +Comment[sk]=LibArchive modul pre Kerfuffle +Comment[sl]=Vstavek LibArchive za Kerfuffle +Comment[sq]=LibArchive Plugin për Kerfuffle +Comment[sr]=Прикључак libarchive за Керфафл +Comment[sr@ijekavian]=Прикључак libarchive за Керфафл +Comment[sr@ijekavianlatin]=Priključak libarchive za Kerfuffle +Comment[sr@latin]=Priključak libarchive za Kerfuffle +Comment[sv]=Libarchive-insticksprogram för Kerfuffle +Comment[ta]=கெர்ஃபஃபில் க்கு லிப்ஆர்கைவ் சொருகி +Comment[th]=ส่วนเสริมของ Kerfuffle สำหรับใช้งานไลบรารี LibArchive +Comment[tr]=Kerfuffle için LibArchive eklentisi +Comment[uk]=Додаток LibArchive для Kerfuffle +Comment[x-test]=xxLibArchive Plugin for Kerfufflexx +Comment[zh_CN]=Kerfuffle 的 LibArchive 插件 +Comment[zh_TW]=Kerfuffle 的 LibArchive 外掛程式 +MimeType=@SUPPORTED_LIBARCHIVE_READWRITE_MIMETYPES@ diff --git a/ark/plugins/libarchive/kerfuffle_libarchive_readonly.desktop.cmake b/ark/plugins/libarchive/kerfuffle_libarchive_readonly.desktop.cmake new file mode 100644 index 00000000..254c4435 --- /dev/null +++ b/ark/plugins/libarchive/kerfuffle_libarchive_readonly.desktop.cmake @@ -0,0 +1,66 @@ +[Desktop Entry] +Type=Service +X-KDE-ServiceTypes=Kerfuffle/Plugin +X-KDE-Library=kerfuffle_libarchive +X-KDE-PluginInfo-Author=Henrique Pinto +X-KDE-PluginInfo-Email=henrique.pinto@kdemail.net +X-KDE-PluginInfo-Name=kerfuffle_libarchive +X-KDE-PluginInfo-Version=0.0.1 +X-KDE-PluginInfo-Website=http://www.kde.org +X-KDE-PluginInfo-License=BSD +X-KDE-Priority=100 +X-KDE-Kerfuffle-APIRevision=1 +X-KDE-Kerfuffle-ReadWrite=false +Name=kerfuffle_libarchive_readonly +Name[ar]=kerfuffle_libarchive_readonly +Name[bg]=kerfuffle_libarchive_readonly +Name[bs]=kerfuffle_libarchive_readonly +Name[ca]=kerfuffle_libarchive_readonly +Name[ca@valencia]=kerfuffle_libarchive_readonly +Name[cs]=kerfuffle_libarchive_readonly +Name[da]=kerfuffle_libarchive_readonly +Name[de]=kerfuffle_libarchive_readonly +Name[el]=kerfuffle_libarchive_readonly +Name[en_GB]=kerfuffle_libarchive_readonly +Name[es]=kerfuffle_libarchive_readonly +Name[et]=kerfuffle_libarchive_readonly +Name[eu]=kerfuffle_libarchive_readonly +Name[fi]=kerfuffle_libarchive_readonly +Name[fr]=kerfuffle_libarchive_readonly +Name[ga]=kerfuffle_libarchive_readonly +Name[gl]=kerfuffle_libarchive_readonly +Name[hr]=kerfuffle_libarchive_readonly +Name[hu]=kerfuffle_libarchive_readonly +Name[ia]=kerfuffle_libarchive_de_sol_lectura +Name[it]=kerfuffle_libarchive_readonly +Name[ja]=kerfuffle_libarchive_readonly +Name[kk]=kerfuffle_libarchive_readonly +Name[km]=kerfuffle_libarchive_readonly +Name[ko]=kerfuffle_libarchive_readonly +Name[lt]=kerfuffle_libarchive_readonly +Name[lv]=kerfuffle_libarchive_readonly +Name[mr]=कर्फफल_लिब-संग्रह_फक्त वाचण्यासाठी +Name[nb]=kerfuffle_libarchive_readonly +Name[nds]=kerfuffle_libarchive_readonly +Name[nl]=kerfuffle_libarchive_readonly +Name[pa]=kerfuffle_libarchive_readonly +Name[pl]=kerfuffle_libarchive_readonly +Name[pt]=kerfuffle_libarchive_readonly +Name[pt_BR]=kerfuffle_libarchive_readonly +Name[ro]=kerfuffle_libarchive_readonly +Name[ru]=kerfuffle_libarchive_readonly +Name[sk]=kerfuffle_libarchive_readonly +Name[sl]=kerfuffle_libarchive_readonly +Name[sr]=kerfuffle_libarchive_readonly +Name[sr@ijekavian]=kerfuffle_libarchive_readonly +Name[sr@ijekavianlatin]=kerfuffle_libarchive_readonly +Name[sr@latin]=kerfuffle_libarchive_readonly +Name[sv]=Kerfuffle Libarchive skrivskyddat +Name[th]=kerfuffle_libarchive_readonly +Name[tr]=kerfuffle_libarchive_readonly +Name[uk]=kerfuffle_libarchive_readonly +Name[wa]=kerfuffle_libarchive_readonly +Name[x-test]=xxkerfuffle_libarchive_readonlyxx +Name[zh_CN]=kerfuffle_libarchive_readonly +Name[zh_TW]=kerfuffle_libarchive_readonly +MimeType=@SUPPORTED_LIBARCHIVE_READONLY_MIMETYPES@ diff --git a/ark/plugins/libarchive/libarchivehandler.cpp b/ark/plugins/libarchive/libarchivehandler.cpp new file mode 100644 index 00000000..0fba5286 --- /dev/null +++ b/ark/plugins/libarchive/libarchivehandler.cpp @@ -0,0 +1,791 @@ +/* + * Copyright (c) 2007 Henrique Pinto + * Copyright (c) 2008-2009 Harald Hvaal + * Copyright (c) 2010 Raphael Kubo da Costa + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ( INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION ) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +#include "libarchivehandler.h" +#include "kerfuffle/kerfuffle_export.h" +#include "kerfuffle/queries.h" + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +struct LibArchiveInterface::ArchiveReadCustomDeleter +{ + static inline void cleanup(struct archive *a) + { + if (a) { + archive_read_finish(a); + } + } +}; + +struct LibArchiveInterface::ArchiveWriteCustomDeleter +{ + static inline void cleanup(struct archive *a) + { + if (a) { + archive_write_finish(a); + } + } +}; + +LibArchiveInterface::LibArchiveInterface(QObject *parent, const QVariantList & args) + : ReadWriteArchiveInterface(parent, args) + , m_cachedArchiveEntryCount(0) + , m_emitNoEntries(false) + , m_extractedFilesSize(0) + , m_workDir(QDir::current()) + , m_archiveReadDisk(archive_read_disk_new()) + , m_abortOperation(false) +{ + archive_read_disk_set_standard_lookup(m_archiveReadDisk.data()); +} + +LibArchiveInterface::~LibArchiveInterface() +{ +} + +bool LibArchiveInterface::list() +{ + kDebug(); + + ArchiveRead arch_reader(archive_read_new()); + + if (!(arch_reader.data())) { + return false; + } + + if (archive_read_support_compression_all(arch_reader.data()) != ARCHIVE_OK) { + return false; + } + + if (archive_read_support_format_all(arch_reader.data()) != ARCHIVE_OK) { + return false; + } + + if (archive_read_open_filename(arch_reader.data(), QFile::encodeName(filename()), 10240) != ARCHIVE_OK) { + emit error(i18nc("@info", "Could not open the archive %1, libarchive cannot handle it.", + filename())); + return false; + } + + m_cachedArchiveEntryCount = 0; + m_extractedFilesSize = 0; + + struct archive_entry *aentry; + int result; + + while (!m_abortOperation && (result = archive_read_next_header(arch_reader.data(), &aentry)) == ARCHIVE_OK) { + if (!m_emitNoEntries) { + emitEntryFromArchiveEntry(aentry); + } + + m_extractedFilesSize += (qlonglong)archive_entry_size(aentry); + + m_cachedArchiveEntryCount++; + archive_read_data_skip(arch_reader.data()); + } + m_abortOperation = false; + + if (result != ARCHIVE_EOF) { + emit error(i18nc("@info", "The archive reading failed with the following error: %1", + QLatin1String( archive_error_string(arch_reader.data())))); + return false; + } + + return archive_read_close(arch_reader.data()) == ARCHIVE_OK; +} + +bool LibArchiveInterface::doKill() +{ + m_abortOperation = true; + return true; +} + +bool LibArchiveInterface::copyFiles(const QVariantList& files, const QString& destinationDirectory, ExtractionOptions options) +{ + kDebug() << "Changing current directory to " << destinationDirectory; + QDir::setCurrent(destinationDirectory); + + const bool extractAll = files.isEmpty(); + const bool preservePaths = options.value(QLatin1String( "PreservePaths" )).toBool(); + + QString rootNode = options.value(QLatin1String("RootNode"), QVariant()).toString(); + if ((!rootNode.isEmpty()) && (!rootNode.endsWith(QLatin1Char('/')))) { + rootNode.append(QLatin1Char('/')); + } + + ArchiveRead arch(archive_read_new()); + + if (!(arch.data())) { + return false; + } + + if (archive_read_support_compression_all(arch.data()) != ARCHIVE_OK) { + return false; + } + + if (archive_read_support_format_all(arch.data()) != ARCHIVE_OK) { + return false; + } + + if (archive_read_open_filename(arch.data(), QFile::encodeName(filename()), 10240) != ARCHIVE_OK) { + emit error(i18nc("@info", "Could not open the archive %1, libarchive cannot handle it.", + filename())); + return false; + } + + ArchiveWrite writer(archive_write_disk_new()); + if (!(writer.data())) { + return false; + } + + archive_write_disk_set_options(writer.data(), extractionFlags()); + + int entryNr = 0; + int totalCount = 0; + + if (extractAll) { + if (!m_cachedArchiveEntryCount) { + emit progress(0); + //TODO: once information progress has been implemented, send + //feedback here that the archive is being read + kDebug() << "For getting progress information, the archive will be listed once"; + m_emitNoEntries = true; + list(); + m_emitNoEntries = false; + } + totalCount = m_cachedArchiveEntryCount; + } else { + totalCount = files.size(); + } + + m_currentExtractedFilesSize = 0; + + bool overwriteAll = false; // Whether to overwrite all files + bool skipAll = false; // Whether to skip all files + struct archive_entry *entry; + + QString fileBeingRenamed; + + while (archive_read_next_header(arch.data(), &entry) == ARCHIVE_OK) { + fileBeingRenamed.clear(); + + // retry with renamed entry, fire an overwrite query again + // if the new entry also exists + retry: + const bool entryIsDir = S_ISDIR(archive_entry_mode(entry)); + + //we skip directories if not preserving paths + if (!preservePaths && entryIsDir) { + archive_read_data_skip(arch.data()); + continue; + } + + //entryName is the name inside the archive, full path + QString entryName = QDir::fromNativeSeparators(QFile::decodeName(archive_entry_pathname(entry))); + + if (entryName.startsWith(QLatin1Char( '/' ))) { + //for now we just can't handle absolute filenames in a tar archive. + //TODO: find out what to do here!! + emit error(i18n("This archive contains archive entries with absolute paths, which are not yet supported by ark.")); + + return false; + } + + if (files.contains(entryName) || entryName == fileBeingRenamed || extractAll) { + // entryFI is the fileinfo pointing to where the file will be + // written from the archive + QFileInfo entryFI(entryName); + //kDebug() << "setting path to " << archive_entry_pathname( entry ); + + const QString fileWithoutPath(entryFI.fileName()); + + //if we DON'T preserve paths, we cut the path and set the entryFI + //fileinfo to the one without the path + if (!preservePaths) { + //empty filenames (ie dirs) should have been skipped already, + //so asserting + Q_ASSERT(!fileWithoutPath.isEmpty()); + + archive_entry_copy_pathname(entry, QFile::encodeName(fileWithoutPath).constData()); + entryFI = QFileInfo(fileWithoutPath); + + //OR, if the commonBase has been set, then we remove this + //common base from the filename + } else if (!rootNode.isEmpty()) { + kDebug() << "Removing" << rootNode << "from" << entryName; + + const QString truncatedFilename(entryName.remove(0, rootNode.size())); + archive_entry_copy_pathname(entry, QFile::encodeName(truncatedFilename).constData()); + + entryFI = QFileInfo(truncatedFilename); + } + + //now check if the file about to be written already exists + if (!entryIsDir && entryFI.exists()) { + if (skipAll) { + archive_read_data_skip(arch.data()); + archive_entry_clear(entry); + continue; + } else if (!overwriteAll && !skipAll) { + Kerfuffle::OverwriteQuery query(entryName); + emit userQuery(&query); + query.waitForResponse(); + + if (query.responseCancelled()) { + archive_read_data_skip(arch.data()); + archive_entry_clear(entry); + break; + } else if (query.responseSkip()) { + archive_read_data_skip(arch.data()); + archive_entry_clear(entry); + continue; + } else if (query.responseAutoSkip()) { + archive_read_data_skip(arch.data()); + archive_entry_clear(entry); + skipAll = true; + continue; + } else if (query.responseRename()) { + const QString newName(query.newFilename()); + fileBeingRenamed = newName; + archive_entry_copy_pathname(entry, QFile::encodeName(newName).constData()); + goto retry; + } else if (query.responseOverwriteAll()) { + overwriteAll = true; + } + } + } + + //if there is an already existing directory: + if (entryIsDir && entryFI.exists()) { + if (entryFI.isWritable()) { + kDebug(1601) << "Warning, existing, but writable dir"; + } else { + kDebug(1601) << "Warning, existing, but non-writable dir. skipping"; + archive_entry_clear(entry); + archive_read_data_skip(arch.data()); + continue; + } + } + + int header_response; + kDebug() << "Writing " << fileWithoutPath << " to " << archive_entry_pathname(entry); + if ((header_response = archive_write_header(writer.data(), entry)) == ARCHIVE_OK) { + //if the whole archive is extracted and the total filesize is + //available, we use partial progress + copyData(arch.data(), writer.data(), (extractAll && m_extractedFilesSize)); + } else if (header_response == ARCHIVE_WARN) { + kDebug() << "Warning while writing " << entryName; + } else { + kDebug() << "Writing header failed with error code " << header_response + << "While attempting to write " << entryName; + } + + //if we only partially extract the archive and the number of + //archive entries is available we use a simple progress based on + //number of items extracted + if (!extractAll && m_cachedArchiveEntryCount) { + ++entryNr; + emit progress(float(entryNr) / totalCount); + } + archive_entry_clear(entry); + } else { + archive_read_data_skip(arch.data()); + } + } + + return archive_read_close(arch.data()) == ARCHIVE_OK; +} + +bool LibArchiveInterface::addFiles(const QStringList& files, const CompressionOptions& options) +{ + const bool creatingNewFile = !QFileInfo(filename()).exists(); + const QString tempFilename = filename() + QLatin1String( ".arkWriting" ); + const QString globalWorkDir = options.value(QLatin1String( "GlobalWorkDir" )).toString(); + + if (!globalWorkDir.isEmpty()) { + kDebug() << "GlobalWorkDir is set, changing dir to " << globalWorkDir; + m_workDir.setPath(globalWorkDir); + QDir::setCurrent(globalWorkDir); + } + + m_writtenFiles.clear(); + + ArchiveRead arch_reader; + if (!creatingNewFile) { + arch_reader.reset(archive_read_new()); + if (!(arch_reader.data())) { + emit error(i18n("The archive reader could not be initialized.")); + return false; + } + + if (archive_read_support_compression_all(arch_reader.data()) != ARCHIVE_OK) { + return false; + } + + if (archive_read_support_format_all(arch_reader.data()) != ARCHIVE_OK) { + return false; + } + + if (archive_read_open_filename(arch_reader.data(), QFile::encodeName(filename()), 10240) != ARCHIVE_OK) { + emit error(i18n("The source file could not be read.")); + return false; + } + } + + ArchiveWrite arch_writer(archive_write_new()); + if (!(arch_writer.data())) { + emit error(i18n("The archive writer could not be initialized.")); + return false; + } + + //pax_restricted is the libarchive default, let's go with that. + archive_write_set_format_pax_restricted(arch_writer.data()); + + int ret; + if (creatingNewFile) { + if (filename().right(2).toUpper() == QLatin1String( "GZ" )) { + kDebug() << "Detected gzip compression for new file"; + ret = archive_write_set_compression_gzip(arch_writer.data()); + } else if (filename().right(3).toUpper() == QLatin1String( "BZ2" )) { + kDebug() << "Detected bzip2 compression for new file"; + ret = archive_write_set_compression_bzip2(arch_writer.data()); +#ifdef HAVE_LIBARCHIVE_XZ_SUPPORT + } else if (filename().right(2).toUpper() == QLatin1String( "XZ" )) { + kDebug() << "Detected xz compression for new file"; + ret = archive_write_set_compression_xz(arch_writer.data()); +#endif +#ifdef HAVE_LIBARCHIVE_LZMA_SUPPORT + } else if (filename().right(4).toUpper() == QLatin1String( "LZMA" )) { + kDebug() << "Detected lzma compression for new file"; + ret = archive_write_set_compression_lzma(arch_writer.data()); +#endif + } else if (filename().right(3).toUpper() == QLatin1String( "TAR" )) { + kDebug() << "Detected no compression for new file (pure tar)"; + ret = archive_write_set_compression_none(arch_writer.data()); + } else { + kDebug() << "Falling back to gzip"; + ret = archive_write_set_compression_gzip(arch_writer.data()); + } + + if (ret != ARCHIVE_OK) { + emit error(i18nc("@info", "Setting the compression method failed with the following error: %1", + QLatin1String(archive_error_string(arch_writer.data())))); + + return false; + } + } else { + switch (archive_compression(arch_reader.data())) { + case ARCHIVE_COMPRESSION_GZIP: + ret = archive_write_set_compression_gzip(arch_writer.data()); + break; + case ARCHIVE_COMPRESSION_BZIP2: + ret = archive_write_set_compression_bzip2(arch_writer.data()); + break; +#ifdef HAVE_LIBARCHIVE_XZ_SUPPORT + case ARCHIVE_COMPRESSION_XZ: + ret = archive_write_set_compression_xz(arch_writer.data()); + break; +#endif +#ifdef HAVE_LIBARCHIVE_LZMA_SUPPORT + case ARCHIVE_COMPRESSION_LZMA: + ret = archive_write_set_compression_lzma(arch_writer.data()); + break; +#endif + case ARCHIVE_COMPRESSION_NONE: + ret = archive_write_set_compression_none(arch_writer.data()); + break; + default: + emit error(i18n("The compression type '%1' is not supported by Ark.", QLatin1String(archive_compression_name(arch_reader.data())))); + return false; + } + + if (ret != ARCHIVE_OK) { + emit error(i18nc("@info", "Setting the compression method failed with the following error: %1", QLatin1String(archive_error_string(arch_writer.data())))); + return false; + } + } + + ret = archive_write_open_filename(arch_writer.data(), QFile::encodeName(tempFilename)); + if (ret != ARCHIVE_OK) { + emit error(i18nc("@info", "Opening the archive for writing failed with the following error: %1", QLatin1String(archive_error_string(arch_writer.data())))); + return false; + } + + //**************** first write the new files + foreach(const QString& selectedFile, files) { + bool success; + + success = writeFile(selectedFile, arch_writer.data()); + + if (!success) { + QFile::remove(tempFilename); + return false; + } + + if (QFileInfo(selectedFile).isDir()) { + QDirIterator it(selectedFile, + QDir::AllEntries | QDir::Readable | + QDir::Hidden | QDir::NoDotAndDotDot, + QDirIterator::Subdirectories); + + while (it.hasNext()) { + const QString path = it.next(); + + if ((it.fileName() == QLatin1String("..")) || + (it.fileName() == QLatin1String("."))) { + continue; + } + + success = writeFile(path + + (it.fileInfo().isDir() ? QLatin1String( "/" ) : QLatin1String( "" )), + arch_writer.data()); + + if (!success) { + QFile::remove(tempFilename); + return false; + } + } + } + } + + struct archive_entry *entry; + + //and if we have old elements... + if (!creatingNewFile) { + //********** copy old elements from previous archive to new archive + while (archive_read_next_header(arch_reader.data(), &entry) == ARCHIVE_OK) { + if (m_writtenFiles.contains(QFile::decodeName(archive_entry_pathname(entry)))) { + archive_read_data_skip(arch_reader.data()); + kDebug() << "Entry already existing, will be refresh: ===> " << archive_entry_pathname(entry); + continue; + } + + int header_response; + //kDebug() << "Writing entry " << fn; + if ((header_response = archive_write_header(arch_writer.data(), entry)) == ARCHIVE_OK) { + //if the whole archive is extracted and the total filesize is + //available, we use partial progress + copyData(arch_reader.data(), arch_writer.data(), false); + } else { + kDebug() << "Writing header failed with error code " << header_response; + QFile::remove(tempFilename); + return false; + } + + archive_entry_clear(entry); + } + + //everything seems OK, so we remove the source file and replace it with + //the new one. + //TODO: do some extra checks to see if this is really OK + QFile::remove(filename()); + } + + QFile::rename(tempFilename, filename()); + + return true; +} + +bool LibArchiveInterface::deleteFiles(const QVariantList& files) +{ + const QString tempFilename = filename() + QLatin1String( ".arkWriting" ); + + ArchiveRead arch_reader(archive_read_new()); + if (!(arch_reader.data())) { + emit error(i18n("The archive reader could not be initialized.")); + return false; + } + + if (archive_read_support_compression_all(arch_reader.data()) != ARCHIVE_OK) { + return false; + } + + if (archive_read_support_format_all(arch_reader.data()) != ARCHIVE_OK) { + return false; + } + + if (archive_read_open_filename(arch_reader.data(), QFile::encodeName(filename()), 10240) != ARCHIVE_OK) { + emit error(i18n("The source file could not be read.")); + return false; + } + + ArchiveWrite arch_writer(archive_write_new()); + if (!(arch_writer.data())) { + emit error(i18n("The archive writer could not be initialized.")); + return false; + } + + //pax_restricted is the libarchive default, let's go with that. + archive_write_set_format_pax_restricted(arch_writer.data()); + + int ret; + switch (archive_compression(arch_reader.data())) { + case ARCHIVE_COMPRESSION_GZIP: + ret = archive_write_set_compression_gzip(arch_writer.data()); + break; + case ARCHIVE_COMPRESSION_BZIP2: + ret = archive_write_set_compression_bzip2(arch_writer.data()); + break; +#ifdef HAVE_LIBARCHIVE_XZ_SUPPORT + case ARCHIVE_COMPRESSION_XZ: + ret = archive_write_set_compression_xz(arch_writer.data()); + break; +#endif +#ifdef HAVE_LIBARCHIVE_LZMA_SUPPORT + case ARCHIVE_COMPRESSION_LZMA: + ret = archive_write_set_compression_lzma(arch_writer.data()); + break; +#endif + case ARCHIVE_COMPRESSION_NONE: + ret = archive_write_set_compression_none(arch_writer.data()); + break; + default: + emit error(i18n("The compression type '%1' is not supported by Ark.", QLatin1String(archive_compression_name(arch_reader.data())))); + return false; + } + + if (ret != ARCHIVE_OK) { + emit error(i18nc("@info", "Setting the compression method failed with the following error: %1", QLatin1String(archive_error_string(arch_writer.data())))); + return false; + } + + ret = archive_write_open_filename(arch_writer.data(), QFile::encodeName(tempFilename)); + if (ret != ARCHIVE_OK) { + emit error(i18nc("@info", "Opening the archive for writing failed with the following error: %1", QLatin1String(archive_error_string(arch_writer.data())))); + return false; + } + + struct archive_entry *entry; + + //********** copy old elements from previous archive to new archive + while (archive_read_next_header(arch_reader.data(), &entry) == ARCHIVE_OK) { + if (files.contains(QFile::decodeName(archive_entry_pathname(entry)))) { + archive_read_data_skip(arch_reader.data()); + kDebug() << "Entry to be deleted, skipping" + << archive_entry_pathname(entry); + emit entryRemoved(QFile::decodeName(archive_entry_pathname(entry))); + continue; + } + + int header_response; + //kDebug() << "Writing entry " << fn; + if ((header_response = archive_write_header(arch_writer.data(), entry)) == ARCHIVE_OK) { + //if the whole archive is extracted and the total filesize is + //available, we use partial progress + copyData(arch_reader.data(), arch_writer.data(), false); + } else { + kDebug() << "Writing header failed with error code " << header_response; + return false; + } + } + + //everything seems OK, so we remove the source file and replace it with + //the new one. + //TODO: do some extra checks to see if this is really OK + QFile::remove(filename()); + QFile::rename(tempFilename, filename()); + + return true; +} + +void LibArchiveInterface::emitEntryFromArchiveEntry(struct archive_entry *aentry) +{ + ArchiveEntry e; + +#ifdef _MSC_VER + e[FileName] = QDir::fromNativeSeparators(QString::fromUtf16((ushort*)archive_entry_pathname_w(aentry))); +#else + e[FileName] = QDir::fromNativeSeparators(QString::fromWCharArray(archive_entry_pathname_w(aentry))); +#endif + e[InternalID] = e[FileName]; + + const QString owner = QString::fromAscii(archive_entry_uname(aentry)); + if (!owner.isEmpty()) { + e[Owner] = owner; + } + + const QString group = QString::fromAscii(archive_entry_gname(aentry)); + if (!group.isEmpty()) { + e[Group] = group; + } + + e[Size] = (qlonglong)archive_entry_size(aentry); + e[IsDirectory] = S_ISDIR(archive_entry_mode(aentry)); + + if (archive_entry_symlink(aentry)) { + e[Link] = QLatin1String( archive_entry_symlink(aentry) ); + } + + e[Timestamp] = QDateTime::fromTime_t(archive_entry_mtime(aentry)); + + emit entry(e); +} + +int LibArchiveInterface::extractionFlags() const +{ + int result = ARCHIVE_EXTRACT_TIME; + result |= ARCHIVE_EXTRACT_SECURE_NODOTDOT; + + // TODO: Don't use arksettings here + /*if ( ArkSettings::preservePerms() ) + { + result &= ARCHIVE_EXTRACT_PERM; + } + + if ( !ArkSettings::extractOverwrite() ) + { + result &= ARCHIVE_EXTRACT_NO_OVERWRITE; + }*/ + + return result; +} + +void LibArchiveInterface::copyData(const QString& filename, struct archive *dest, bool partialprogress) +{ + char buff[10240]; + ssize_t readBytes; + QFile file(filename); + + if (!file.open(QIODevice::ReadOnly)) { + return; + } + + readBytes = file.read(buff, sizeof(buff)); + while (readBytes > 0) { + /* int writeBytes = */ + archive_write_data(dest, buff, readBytes); + if (archive_errno(dest) != ARCHIVE_OK) { + kDebug() << "Error while writing..." << archive_error_string(dest) << "(error nb =" << archive_errno(dest) << ')'; + return; + } + + if (partialprogress) { + m_currentExtractedFilesSize += readBytes; + emit progress(float(m_currentExtractedFilesSize) / m_extractedFilesSize); + } + + readBytes = file.read(buff, sizeof(buff)); + } + + file.close(); +} + +void LibArchiveInterface::copyData(struct archive *source, struct archive *dest, bool partialprogress) +{ + char buff[10240]; + ssize_t readBytes; + + readBytes = archive_read_data(source, buff, sizeof(buff)); + while (readBytes > 0) { + /* int writeBytes = */ + archive_write_data(dest, buff, readBytes); + if (archive_errno(dest) != ARCHIVE_OK) { + kDebug() << "Error while extracting..." << archive_error_string(dest) << "(error nb =" << archive_errno(dest) << ')'; + return; + } + + if (partialprogress) { + m_currentExtractedFilesSize += readBytes; + emit progress(float(m_currentExtractedFilesSize) / m_extractedFilesSize); + } + + readBytes = archive_read_data(source, buff, sizeof(buff)); + } +} + +// TODO: if we merge this with copyData(), we can pass more data +// such as an fd to archive_read_disk_entry_from_file() +bool LibArchiveInterface::writeFile(const QString& fileName, struct archive* arch_writer) +{ + int header_response; + + const bool trailingSlash = fileName.endsWith(QLatin1Char( '/' )); + + // #191821: workDir must be used instead of QDir::current() + // so that symlinks aren't resolved automatically + // TODO: this kind of call should be moved upwards in the + // class hierarchy to avoid code duplication + const QString relativeName = m_workDir.relativeFilePath(fileName) + (trailingSlash ? QLatin1String( "/" ) : QLatin1String( "" )); + + // #253059: Even if we use archive_read_disk_entry_from_file, + // libarchive may have been compiled without HAVE_LSTAT, + // or something may have caused it to follow symlinks, in + // which case stat() will be called. To avoid this, we + // call lstat() ourselves. + KDE_struct_stat st; + KDE_lstat(QFile::encodeName(fileName).constData(), &st); + + struct archive_entry *entry = archive_entry_new(); + archive_entry_set_pathname(entry, QFile::encodeName(relativeName).constData()); + archive_entry_copy_sourcepath(entry, QFile::encodeName(fileName).constData()); + archive_read_disk_entry_from_file(m_archiveReadDisk.data(), entry, -1, &st); + + kDebug() << "Writing new entry " << archive_entry_pathname(entry); + if ((header_response = archive_write_header(arch_writer, entry)) == ARCHIVE_OK) { + //if the whole archive is extracted and the total filesize is + //available, we use partial progress + copyData(fileName, arch_writer, false); + } else { + kDebug() << "Writing header failed with error code " << header_response; + kDebug() << "Error while writing..." << archive_error_string(arch_writer) << "(error nb =" << archive_errno(arch_writer) << ')'; + + emit error(i18nc("@info Error in a message box", + "Ark could not compress %1:%2", + fileName, + QLatin1String(archive_error_string(arch_writer)))); + + archive_entry_free(entry); + + return false; + } + + m_writtenFiles.push_back(relativeName); + + emitEntryFromArchiveEntry(entry); + + archive_entry_free(entry); + + return true; +} + +KERFUFFLE_EXPORT_PLUGIN(LibArchiveInterface) + +#include "libarchivehandler.moc" diff --git a/ark/plugins/libarchive/libarchivehandler.h b/ark/plugins/libarchive/libarchivehandler.h new file mode 100644 index 00000000..088c0fe8 --- /dev/null +++ b/ark/plugins/libarchive/libarchivehandler.h @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2007 Henrique Pinto + * Copyright (c) 2008-2009 Harald Hvaal + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ( INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION ) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef LIBARCHIVEHANDLER_H +#define LIBARCHIVEHANDLER_H + +#include "kerfuffle/archiveinterface.h" + +#include +#include +#include +#include + +using namespace Kerfuffle; + +class LibArchiveInterface: public ReadWriteArchiveInterface +{ + Q_OBJECT + +public: + explicit LibArchiveInterface(QObject *parent, const QVariantList& args); + ~LibArchiveInterface(); + + bool list(); + bool doKill(); + bool copyFiles(const QVariantList& files, const QString& destinationDirectory, ExtractionOptions options); + bool addFiles(const QStringList& files, const CompressionOptions& options); + bool deleteFiles(const QVariantList& files); + +private: + void emitEntryFromArchiveEntry(struct archive_entry *entry); + int extractionFlags() const; + void copyData(const QString& filename, struct archive *dest, bool partialprogress = true); + void copyData(struct archive *source, struct archive *dest, bool partialprogress = true); + bool writeFile(const QString& fileName, struct archive* arch); + + struct ArchiveReadCustomDeleter; + struct ArchiveWriteCustomDeleter; + typedef QScopedPointer ArchiveRead; + typedef QScopedPointer ArchiveWrite; + + int m_cachedArchiveEntryCount; + qlonglong m_currentExtractedFilesSize; + bool m_emitNoEntries; + qlonglong m_extractedFilesSize; + QDir m_workDir; + QStringList m_writtenFiles; + ArchiveRead m_archiveReadDisk; + bool m_abortOperation; +}; + +#endif // LIBARCHIVEHANDLER_H diff --git a/ark/plugins/libsinglefileplugin/CMakeLists.txt b/ark/plugins/libsinglefileplugin/CMakeLists.txt new file mode 100644 index 00000000..93262971 --- /dev/null +++ b/ark/plugins/libsinglefileplugin/CMakeLists.txt @@ -0,0 +1,69 @@ +set(kerfuffle_singlefile_SRCS singlefileplugin.cpp) +set(SUPPORTED_LIBSINGLEFILE_MIMETYPES "") + +# This MIME type was originally set in ark.desktop but is does not +# seem to be supported anywhere. Assuming that, if it were supported, +# it would be here. +set(SUPPORTED_LIBSINGLEFILE_MIMETYPES "${SUPPORTED_LIBSINGLEFILE_MIMETYPES}application/x-compress;") + +# +# GZip files +# +macro_optional_find_package(ZLIB) + +macro_log_feature(ZLIB_FOUND "ZLib" "The Zlib compression library" "http://www.zlib.net" FALSE "" "Required for the .gz format support in Ark") + +if (ZLIB_FOUND) + set(kerfuffle_libgz_SRCS gzplugin.cpp ${kerfuffle_singlefile_SRCS}) + set(SUPPORTED_LIBSINGLEFILE_MIMETYPES "${SUPPORTED_LIBSINGLEFILE_MIMETYPES}application/x-gzip;") + + kde4_add_plugin(kerfuffle_libgz ${kerfuffle_libgz_SRCS}) + + target_link_libraries(kerfuffle_libgz ${KDE4_KDECORE_LIBS} ${KDE4_KIO_LIBS} kerfuffle ) + + install(TARGETS kerfuffle_libgz DESTINATION ${PLUGIN_INSTALL_DIR} ) + + install( FILES kerfuffle_libgz.desktop DESTINATION ${SERVICES_INSTALL_DIR} ) +endif (ZLIB_FOUND) + +# +# Bzip2 files +# +macro_optional_find_package(BZip2) + +macro_log_feature(BZIP2_FOUND "BZip2" "A high-quality data compressor" "http://www.bzip.org" FALSE "" "Required for the .bz2 format support in Ark") + +if (BZIP2_FOUND) + set(kerfuffle_libbz2_SRCS bz2plugin.cpp ${kerfuffle_singlefile_SRCS}) + set(SUPPORTED_LIBSINGLEFILE_MIMETYPES "${SUPPORTED_LIBSINGLEFILE_MIMETYPES}application/x-bzip;application/x-bzip2;") + + kde4_add_plugin(kerfuffle_libbz2 ${kerfuffle_libbz2_SRCS}) + + target_link_libraries(kerfuffle_libbz2 ${KDE4_KDECORE_LIBS} ${KDE4_KIO_LIBS} kerfuffle ) + + install(TARGETS kerfuffle_libbz2 DESTINATION ${PLUGIN_INSTALL_DIR} ) + + install( FILES kerfuffle_libbz2.desktop DESTINATION ${SERVICES_INSTALL_DIR} ) +endif (BZIP2_FOUND) + +# +# LZMA files +# +macro_optional_find_package(LibLZMA) + +macro_log_feature(LIBLZMA_FOUND "LibLZMA" "Liblzma is used for the .xz and .lzma formats" "http://tukaani.org/xz/" FALSE "" "Required for the .xz and .lzma format support in Ark") + +if (LIBLZMA_FOUND) + set(kerfuffle_libxz_SRCS xzplugin.cpp ${kerfuffle_singlefile_SRCS}) + set(SUPPORTED_LIBSINGLEFILE_MIMETYPES "${SUPPORTED_LIBSINGLEFILE_MIMETYPES}application/x-lzma;application/x-xz;") + + kde4_add_plugin(kerfuffle_libxz ${kerfuffle_libxz_SRCS}) + + target_link_libraries(kerfuffle_libxz ${KDE4_KDECORE_LIBS} ${KDE4_KIO_LIBS} kerfuffle ) + + install(TARGETS kerfuffle_libxz DESTINATION ${PLUGIN_INSTALL_DIR} ) + + install( FILES kerfuffle_libxz.desktop DESTINATION ${SERVICES_INSTALL_DIR} ) +endif (LIBLZMA_FOUND) + +set(SUPPORTED_ARK_MIMETYPES "${SUPPORTED_ARK_MIMETYPES}${SUPPORTED_LIBSINGLEFILE_MIMETYPES}" PARENT_SCOPE) diff --git a/ark/plugins/libsinglefileplugin/bz2plugin.cpp b/ark/plugins/libsinglefileplugin/bz2plugin.cpp new file mode 100644 index 00000000..de9c9b41 --- /dev/null +++ b/ark/plugins/libsinglefileplugin/bz2plugin.cpp @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2009 Raphael Kubo da Costa + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ( INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION ) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "bz2plugin.h" +#include "kerfuffle/kerfuffle_export.h" + +#include + +LibBzip2Interface::LibBzip2Interface(QObject *parent, const QVariantList & args) + : LibSingleFileInterface(parent, args) +{ + m_mimeType = QLatin1String( "application/x-bzip" ); + m_possibleExtensions.append(QLatin1String( ".bz2" )); +} + +LibBzip2Interface::~LibBzip2Interface() +{ +} + +KERFUFFLE_EXPORT_PLUGIN(LibBzip2Interface) + +#include "bz2plugin.moc" diff --git a/ark/plugins/libsinglefileplugin/bz2plugin.h b/ark/plugins/libsinglefileplugin/bz2plugin.h new file mode 100644 index 00000000..94f7d3c1 --- /dev/null +++ b/ark/plugins/libsinglefileplugin/bz2plugin.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2009 Raphael Kubo da Costa + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ( INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION ) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef BZ2PLUGIN_H +#define BZ2PLUGIN_H + +#include "singlefileplugin.h" + +class LibBzip2Interface : public LibSingleFileInterface +{ + Q_OBJECT + +public: + LibBzip2Interface(QObject *parent, const QVariantList & args); + virtual ~LibBzip2Interface(); +}; + +#endif // BZ2PLUGIN_H diff --git a/ark/plugins/libsinglefileplugin/gzplugin.cpp b/ark/plugins/libsinglefileplugin/gzplugin.cpp new file mode 100644 index 00000000..469432a7 --- /dev/null +++ b/ark/plugins/libsinglefileplugin/gzplugin.cpp @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2009 Raphael Kubo da Costa + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ( INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION ) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "gzplugin.h" +#include "kerfuffle/kerfuffle_export.h" + +#include + +LibGzipInterface::LibGzipInterface(QObject *parent, const QVariantList & args) + : LibSingleFileInterface(parent, args) +{ + m_mimeType = QLatin1String( "application/x-gzip" ); + m_possibleExtensions.append(QLatin1String( ".gz" )); +} + +LibGzipInterface::~LibGzipInterface() +{ +} + +KERFUFFLE_EXPORT_PLUGIN(LibGzipInterface) + +#include "gzplugin.moc" diff --git a/ark/plugins/libsinglefileplugin/gzplugin.h b/ark/plugins/libsinglefileplugin/gzplugin.h new file mode 100644 index 00000000..7ad5ef79 --- /dev/null +++ b/ark/plugins/libsinglefileplugin/gzplugin.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2009 Raphael Kubo da Costa + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ( INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION ) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef GZPLUGIN_H +#define GZPLUGIN_H + +#include "singlefileplugin.h" + +class LibGzipInterface : public LibSingleFileInterface +{ + Q_OBJECT + +public: + LibGzipInterface(QObject *parent, const QVariantList & args); + virtual ~LibGzipInterface(); +}; + +#endif // GZPLUGIN_H diff --git a/ark/plugins/libsinglefileplugin/kerfuffle_libbz2.desktop b/ark/plugins/libsinglefileplugin/kerfuffle_libbz2.desktop new file mode 100644 index 00000000..173f37d4 --- /dev/null +++ b/ark/plugins/libsinglefileplugin/kerfuffle_libbz2.desktop @@ -0,0 +1,124 @@ +[Desktop Entry] +Type=Service +X-KDE-ServiceTypes=Kerfuffle/Plugin +X-KDE-Library=kerfuffle_libbz2 +X-KDE-PluginInfo-Author=Raphael Kubo da Costa +X-KDE-PluginInfo-Email=rakuco@FreeBSD.org +X-KDE-PluginInfo-Name=kerfuffle_libbz2 +X-KDE-PluginInfo-Version=0.0.1 +X-KDE-PluginInfo-Website=http://www.kde.org +X-KDE-PluginInfo-License=BSD +X-KDE-Priority=100 +X-KDE-Kerfuffle-APIRevision=1 +#X-KDE-Kerfuffle-ReadWrite=true +Name=kerfuffle_libbz2 +Name[ar]=kerfuffle_libbz2 +Name[ast]=kerfuffle_libbz2 +Name[bg]=kerfuffle_libbz2 +Name[bs]=kerfuffle_libbz2 +Name[ca]=kerfuffle_libbz2 +Name[ca@valencia]=kerfuffle_libbz2 +Name[cs]=kerfuffle_libbz2 +Name[da]=kerfuffle_libbz2 +Name[de]=kerfuffle_libbz2 +Name[el]=kerfuffle_libbz2 +Name[en_GB]=kerfuffle_libbz2 +Name[es]=kerfuffle_libbz2 +Name[et]=kerfuffle_libbz2 +Name[eu]=kerfuffle_libbz2 +Name[fi]=kerfuffle_libbz2 +Name[fr]=kerfuffle_libbz2 +Name[ga]=kerfuffle_libbz2 +Name[gl]=kerfuffle_libbz2 +Name[hr]=kerfuffle_libbz2 +Name[hu]=kerfuffle_libbz2 +Name[ia]=kerfuffle_libbz2 +Name[id]=kerfuffle_libbz2 +Name[it]=kerfuffle_libbz2 +Name[ja]=kerfuffle_libbz2 +Name[kk]=kerfuffle_libbz2 +Name[km]=kerfuffle_libbz2 +Name[ko]=kerfuffle_libbz2 +Name[lt]=kerfuffle_libbz2 +Name[lv]=kerfuffle_libbz2 +Name[mr]=कर्फफल_लिब-बिझेड२ +Name[nb]=kerfuffle_libbz2 +Name[nds]=kerfuffle_libbz2 +Name[nl]=kerfuffle_libbz2 +Name[nn]=kerfuffle_libbz2 +Name[pa]=kerfuffle_libbz2 +Name[pl]=kerfuffle_libbz2 +Name[pt]=kerfuffle_libbz2 +Name[pt_BR]=kerfuffle_libbz2 +Name[ro]=kerfuffle_libbz2 +Name[ru]=kerfuffle_libbz2 +Name[sk]=kerfuffle_libbz2 +Name[sl]=kerfuffle_libbz2 +Name[sq]=kerfuffle_libbz2 +Name[sr]=kerfuffle_libbz2 +Name[sr@ijekavian]=kerfuffle_libbz2 +Name[sr@ijekavianlatin]=kerfuffle_libbz2 +Name[sr@latin]=kerfuffle_libbz2 +Name[sv]=Kerfuffle Libbz2 +Name[th]=kerfuffle_libbz2 +Name[tr]=kerfuffle_libbz2 +Name[uk]=kerfuffle_libbz2 +Name[wa]=kerfuffle_libbz2 +Name[x-test]=xxkerfuffle_libbz2xx +Name[zh_CN]=kerfuffle_libbz2 +Name[zh_TW]=kerfuffle_libbz2 +Comment=libbz2 plugin for Kerfuffle +Comment[ar]=ملحق libbz2 لـ Kerfuffle +Comment[ast]=Complementu libbz2 pa Kerfuffle +Comment[bg]=libbz2 приставка за Kerfuffle +Comment[bs]=Priključak libbz2 za Kerfafl +Comment[ca]=Connector del libbz2 pel Kerfuffle +Comment[ca@valencia]=Connector del libbz2 pel Kerfuffle +Comment[cs]=libbz2 modul pro Kerfuffle +Comment[da]=libbz2-plugin til Kerfuffle +Comment[de]=libgz2-Modul für Kerfuffle +Comment[el]=πρόσθετο libz2 για τo Kerfuffle +Comment[en_GB]=libbz2 plugin for Kerfuffle +Comment[es]=Complemento libbz2 para Kerfuffle +Comment[et]=Kerfuffle'i libbz2 plugin +Comment[eu]=Kerfuffle-rentzako libbz2 plugina +Comment[fi]=libbz2-pakkaustuki +Comment[fr]=Module externe « libbz2 » pour Kerfuffle +Comment[ga]=Breiseán libgz le haghaidh Kerfuffle +Comment[gl]=Extensión de libbz2 para Kerfuffle +Comment[hr]=Priključak libbz2 za Kerfuffle +Comment[hu]=Libbz2 Kerfuffle-modul +Comment[ia]=plugin de libbz2 per Kerfuffle +Comment[id]=Pengaya libbz2 untuk Kerfuffle +Comment[it]=Estensione libbz2 per Kerfuffle +Comment[ja]=Kerfuffle のための libgz2 プラグイン +Comment[kk]=Kerfuffle-ге арналған libbz2 плагині +Comment[km]=កម្មវិធី​ជំនួយ libbz2 សម្រាប់ Kerfuffle +Comment[ko]=Kerfuffle을 위한 libbz2 플러그인 +Comment[lt]=libbz2 Kerfuffle priedas +Comment[lv]=Kerfuffle libz2 spraudnis +Comment[mr]=कर्फफल करिता लिब-बिझेड२ प्लगइन +Comment[nb]=libbz2 pprogramtillegg for Kerfuffle +Comment[nds]=Libbz2-Moduul för Kerfuffle +Comment[nl]=libbz2 plug-in voor Kerfuffle +Comment[nn]=libzip2-programtillegg til Kerfuffle +Comment[pl]=wtyczka libbz2 dla Kerfuffle +Comment[pt]='Plugin' da 'libbz2' para o Kerfuffle +Comment[pt_BR]=Plugin libbz2 para o Kerfuffle +Comment[ro]=Modul libbz2 pentru Kerfuffle +Comment[ru]=Модуль libbz2 для Kerfuffle +Comment[sk]=libbz2 modul pre Kerfuffle +Comment[sl]=Vstavek libbz2 za Kerfuffle +Comment[sq]=libbz2 plugin për Kerfuffle +Comment[sr]=Прикључак libbz2 за Керфафл +Comment[sr@ijekavian]=Прикључак libbz2 за Керфафл +Comment[sr@ijekavianlatin]=Priključak libbz2 za Kerfuffle +Comment[sr@latin]=Priključak libbz2 za Kerfuffle +Comment[sv]=Libbz2-insticksprogram för Kerfuffle +Comment[th]=ส่วนเสริมของ Kerfuffle สำหรับใช้งานไลบรารี libbz2 +Comment[tr]=Kerfuffle için libbz2 eklentisi +Comment[uk]=Додаток libbz2 для Kerfuffle +Comment[x-test]=xxlibbz2 plugin for Kerfufflexx +Comment[zh_CN]=Kerfuffle 的 libbz2 插件 +Comment[zh_TW]=Kerfuffle 的 libbz2 外掛程式 +MimeType=application/x-bzip; diff --git a/ark/plugins/libsinglefileplugin/kerfuffle_libgz.desktop b/ark/plugins/libsinglefileplugin/kerfuffle_libgz.desktop new file mode 100644 index 00000000..ae60b508 --- /dev/null +++ b/ark/plugins/libsinglefileplugin/kerfuffle_libgz.desktop @@ -0,0 +1,126 @@ +[Desktop Entry] +Type=Service +X-KDE-ServiceTypes=Kerfuffle/Plugin +X-KDE-Library=kerfuffle_libgz +X-KDE-PluginInfo-Author=Harald Hvaal +X-KDE-PluginInfo-Email=haraldhv@stud.ntnu.no +X-KDE-PluginInfo-Name=kerfuffle_libgz +X-KDE-PluginInfo-Version=0.0.1 +X-KDE-PluginInfo-Website=http://www.kde.org +X-KDE-PluginInfo-License=BSD +X-KDE-Priority=100 +X-KDE-Kerfuffle-APIRevision=1 +#X-KDE-Kerfuffle-ReadWrite=true +Name=kerfuffle_libgz +Name[ar]=kerfuffle_libgz +Name[ast]=krfuffle_libgz +Name[bg]=kerfuffle_libgz +Name[bs]=kerfuffle_libgz +Name[ca]=kerfuffle_libgz +Name[ca@valencia]=kerfuffle_libgz +Name[cs]=kerfuffle_libgz +Name[da]=kerfuffle_libgz +Name[de]=kerfuffle_libgz +Name[el]=kerfuffle_libgz +Name[en_GB]=kerfuffle_libgz +Name[es]=kerfuffle_libgz +Name[et]=kerfuffle_libgz +Name[eu]=kerfuffle_libgz +Name[fi]=kerfuffle_libgz +Name[fr]=kerfuffle_libgz +Name[ga]=kerfuffle_libgz +Name[gl]=kerfuffle_libgz +Name[hne]=करफुफल_लिबजीजेड +Name[hr]=kerfuffle_libgz +Name[hu]=kerfuffle_libgz +Name[ia]=kerfuffle_libgz +Name[id]=kerfuffle_libgz +Name[it]=kerfuffle_libgz +Name[ja]=kerfuffle_libgz +Name[kk]=kerfuffle_libgz +Name[km]=kerfuffle_libgz +Name[ko]=kerfuffle_libgz +Name[lt]=kerfuffle_libgz +Name[lv]=kerfuffle_libgz +Name[mr]=कर्फफल_लिब-जिझेड +Name[nb]=kerfuffle_libgz +Name[nds]=kerfuffle_libgz +Name[nl]=kerfuffle_libgz +Name[nn]=kerfuffle_libgz +Name[pa]=kerfuffle_libgz +Name[pl]=kerfuffle_libgz +Name[pt]=kerfuffle_libgz +Name[pt_BR]=kerfuffle_libgz +Name[ro]=kerfuffle_libgz +Name[ru]=kerfuffle_libgz +Name[sk]=kerfuffle_libgz +Name[sl]=kerfuffle_libgz +Name[sq]=kerfuffle_libgz +Name[sr]=kerfuffle_libgz +Name[sr@ijekavian]=kerfuffle_libgz +Name[sr@ijekavianlatin]=kerfuffle_libgz +Name[sr@latin]=kerfuffle_libgz +Name[sv]=Kerfuffle Libgz +Name[th]=kerfuffle_libgz +Name[tr]=kerfuffle_libgz +Name[uk]=kerfuffle_libgz +Name[wa]=kerfuffle_libgz +Name[x-test]=xxkerfuffle_libgzxx +Name[zh_CN]=kerfuffle_libgz +Name[zh_TW]=kerfuffle_libgz +Comment=libgz plugin for Kerfuffle +Comment[ar]=ملحقlibgz لـ Kerfuffle +Comment[ast]=Aplicación libgz pa Kerfuffle +Comment[bg]=Приствка libgz за Kerfuffle +Comment[bs]=Priključak libgz za Kerfafl +Comment[ca]=Connector del libgz pel Kerfuffle +Comment[ca@valencia]=Connector del libgz pel Kerfuffle +Comment[cs]=libgz modul pro Kerfuffle +Comment[da]=libgz-plugin til Kerfuffle +Comment[de]=libgz-Modul für Kerfuffle +Comment[el]=πρόσθετο libgz για τη Kerfuffle +Comment[en_GB]=libgz plugin for Kerfuffle +Comment[es]=Complemento libgz para Kerfuffle +Comment[et]=Kerfuffle'i libgz plugin +Comment[eu]=Kerfuffle-rentzako libgz plugina +Comment[fi]=libgz-pakkaustuki +Comment[fr]=Module externe « libgz » pour Kerfuffle +Comment[ga]=Breiseán libgz le haghaidh Kerfuffle +Comment[gl]=Extensión de libgz para Kerfuffle +Comment[hne]=करफुफल बर लिबजीजेड प्लगइन +Comment[hr]=Priključak libgz za Kerfuffle +Comment[hu]=Libgz Kerfuffle-modul +Comment[ia]=plugin de libgz per Kerfuffle +Comment[id]=Pengaya libgz untuk Kerfuffle +Comment[it]=Estensione libgz per Kerfuffle +Comment[ja]=Kerfuffle のための libgz プラグイン +Comment[kk]=Kerfuffle-ге арналған libgz плагині +Comment[km]=កម្មវិធី​ជំនួយ libgz សម្រាប់ Kerfuffle +Comment[ko]=Kerfuffle을 위한 libgz 플러그인 +Comment[lt]=libgz Kerfuffle priedas +Comment[lv]=Kerfuffle libgz spraudnis +Comment[mr]=कर्फफल करिता लिब-जिझेड प्लगइन +Comment[nb]=libgz pprogramtillegg for Kerfuffle +Comment[nds]=Libgz-Moduul för Kerfuffle +Comment[nl]=libgz-plug-in voor Kerfuffle +Comment[nn]=libgz-programtillegg til Kerfuffle +Comment[pl]=wtyczka libgz dla Kerfuffle +Comment[pt]='Plugin' da 'libgz' para o Kerfuffle +Comment[pt_BR]=Plugin libgz para a Kerfuffle +Comment[ro]=Modul libgz pentru Kerfuffle +Comment[ru]=Модуль libgz для Kerfuffle +Comment[sk]=libgz modul pre Kerfuffle +Comment[sl]=Vstavek libgz za Kerfuffle +Comment[sq]=libgz plugin për Kerfuffle +Comment[sr]=Прикључак libgz за Керфафл +Comment[sr@ijekavian]=Прикључак libgz за Керфафл +Comment[sr@ijekavianlatin]=Priključak libgz za Kerfuffle +Comment[sr@latin]=Priključak libgz za Kerfuffle +Comment[sv]=Libgz-insticksprogram för Kerfuffle +Comment[th]=ส่วนเสริมของ Kerfuffle สำหรับใช้งานไลบรารี libgz +Comment[tr]=Kerfuffle için libgz eklentisi +Comment[uk]=Додаток libgz для Kerfuffle +Comment[x-test]=xxlibgz plugin for Kerfufflexx +Comment[zh_CN]=Kerfuffle 的 libgz 插件 +Comment[zh_TW]=Kerfuffle 的 libgz 外掛程式 +MimeType=application/x-gzip; diff --git a/ark/plugins/libsinglefileplugin/kerfuffle_libxz.desktop b/ark/plugins/libsinglefileplugin/kerfuffle_libxz.desktop new file mode 100644 index 00000000..6cc2f82b --- /dev/null +++ b/ark/plugins/libsinglefileplugin/kerfuffle_libxz.desktop @@ -0,0 +1,124 @@ +[Desktop Entry] +Type=Service +X-KDE-ServiceTypes=Kerfuffle/Plugin +X-KDE-Library=kerfuffle_libxz +X-KDE-PluginInfo-Author=Raphael Kubo da Costa +X-KDE-PluginInfo-Email=rakuco@FreeBSD.org +X-KDE-PluginInfo-Name=kerfuffle_libxz +X-KDE-PluginInfo-Version=0.0.1 +X-KDE-PluginInfo-Website=http://www.kde.org +X-KDE-PluginInfo-License=BSD +X-KDE-Priority=100 +X-KDE-Kerfuffle-APIRevision=1 +#X-KDE-Kerfuffle-ReadWrite=true +Name=kerfuffle_libxz +Name[ar]=kerfuffle_libxz +Name[ast]=kerfuffle_libxz +Name[bg]=kerfuffle_libxz +Name[bs]=kerfuffle_libxz +Name[ca]=kerfuffle_libxz +Name[ca@valencia]=kerfuffle_libxz +Name[cs]=kerfuffle_libxz +Name[da]=kerfuffle_libxz +Name[de]=kerfuffle_libxz +Name[el]=kerfuffle_libxz +Name[en_GB]=kerfuffle_libxz +Name[es]=kerfuffle_libxz +Name[et]=kerfuffle_libxz +Name[eu]=kerfuffle_libxz +Name[fi]=kerfuffle_libxz +Name[fr]=kerfuffle_libxz +Name[ga]=kerfuffle_libxz +Name[gl]=kerfuffle_libxz +Name[hr]=kerfuffle_libxz +Name[hu]=kerfuffle_libxz +Name[ia]=kerfuffle_libxz +Name[id]=kerfuffle_libxz +Name[it]=kerfuffle_libxz +Name[ja]=kerfuffle_libxz +Name[kk]=kerfuffle_libxz +Name[km]=kerfuffle_libxz +Name[ko]=kerfuffle_libxz +Name[lt]=kerfuffle_libxz +Name[lv]=kerfuffle_libxz +Name[mr]=कर्फफल_लिब-एक्सझेड +Name[nb]=kerfuffle_libxz +Name[nds]=kerfuffle_libxz +Name[nl]=kerfuffle_libxz +Name[nn]=kerfuffle_libxz +Name[pa]=kerfuffle_libxz +Name[pl]=kerfuffle_libxz +Name[pt]=kerfuffle_libxz +Name[pt_BR]=kerfuffle_libxz +Name[ro]=kerfuffle_libxz +Name[ru]=kerfuffle_libxz +Name[sk]=kerfuffle_libxz +Name[sl]=kerfuffle_libxz +Name[sq]=kerfuffle_libxz +Name[sr]=kerfuffle_libxz +Name[sr@ijekavian]=kerfuffle_libxz +Name[sr@ijekavianlatin]=kerfuffle_libxz +Name[sr@latin]=kerfuffle_libxz +Name[sv]=Kerfuffle Libxz +Name[th]=kerfuffle_libxz +Name[tr]=kerfuffle_libxz +Name[uk]=kerfuffle_libxz +Name[wa]=kerfuffle_libxz +Name[x-test]=xxkerfuffle_libxzxx +Name[zh_CN]=kerfuffle_libxz +Name[zh_TW]=kerfuffle_libxz +Comment=libxz plugin for Kerfuffle +Comment[ar]=ملحقlibxz لـ Kerfuffle +Comment[ast]=Complementu libxz pa Kerfuffle +Comment[bg]=libxz приставка за Kerfuffle +Comment[bs]=Priključak libxz za Kerfafl +Comment[ca]=Connector del libxz pel Kerfuffle +Comment[ca@valencia]=Connector del libxz pel Kerfuffle +Comment[cs]=libxz modul pro Kerfuffle +Comment[da]=libxz-plugin til Kerfuffle +Comment[de]=libxz-Modul für Kerfuffle +Comment[el]=πρόσθετο libxz για τo Kerfuffle +Comment[en_GB]=libxz plugin for Kerfuffle +Comment[es]=Complemento libxz para Kerfuffle +Comment[et]=Kerfuffle'i libxz plugin +Comment[eu]=Kerfuffle-rentzako libxz plugina +Comment[fi]=libxz-pakkaustuki +Comment[fr]=Module externe « libxz » pour Kerfuffle +Comment[ga]=Breiseán libxz le haghaidh Kerfuffle +Comment[gl]=Extensión de libxz para Kerfuffle +Comment[hr]=Priključak libxz za Kerfuffle +Comment[hu]=Libxz Kerfuffle-modul +Comment[ia]=plugin de libxz per Kerfuffle +Comment[id]=Pengaya libxz untuk Kerfuffle +Comment[it]=Estensione libxz per Kerfuffle +Comment[ja]=Kerfuffle のための libxz プラグイン +Comment[kk]=Kerfuffle-ге арналған libxz плагині +Comment[km]=កម្មវិធី​ជំនួយ​ libxz សម្រាប់ Kerfuffle +Comment[ko]=Kerfuffle을 위한 libxz 플러그인 +Comment[lt]=libxz Kerfuffle priedas +Comment[lv]=Kerfuffle libxz spraudnis +Comment[mr]=कर्फफल करिता लिब-एक्सझेड प्लगइन +Comment[nb]=libxz pprogramtillegg for Kerfuffle +Comment[nds]=Libxz-Moduul för Kerfuffle +Comment[nl]=libxz plug-in voor Kerfuffle +Comment[nn]=libxz-programtillegg til Kerfuffle +Comment[pl]=wtyczka libxz dla Kerfuffle +Comment[pt]='Plugin' da 'libxz' para o Kerfuffle +Comment[pt_BR]=Plugin libxz para o Kerfuffle +Comment[ro]=Modul libxz pentru Kerfuffle +Comment[ru]=Модуль libxz для Kerfuffle +Comment[sk]=libxz modul pre Kerfuffle +Comment[sl]=Vstavek libxz za Kerfuffle +Comment[sq]=libxz plugin për Kerfuffle +Comment[sr]=Прикључак libxz за Керфафл +Comment[sr@ijekavian]=Прикључак libxz за Керфафл +Comment[sr@ijekavianlatin]=Priključak libxz za Kerfuffle +Comment[sr@latin]=Priključak libxz za Kerfuffle +Comment[sv]=Libxz-insticksprogram för Kerfuffle +Comment[th]=ส่วนเสริมของ Kerfuffle สำหรับใช้งานไลบรารี libxz +Comment[tr]=Kerfuffle için libxz eklentisi +Comment[uk]=Додаток libxz для Kerfuffle +Comment[x-test]=xxlibxz plugin for Kerfufflexx +Comment[zh_CN]=Kerfuffle 的 libxz 插件 +Comment[zh_TW]=Kerfuffle 的 libxz 外掛程式 +MimeType=application/x-lzma;application/x-xz; diff --git a/ark/plugins/libsinglefileplugin/singlefileplugin.cpp b/ark/plugins/libsinglefileplugin/singlefileplugin.cpp new file mode 100644 index 00000000..ac5a2f5e --- /dev/null +++ b/ark/plugins/libsinglefileplugin/singlefileplugin.cpp @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2009 Raphael Kubo da Costa + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ( INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION ) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "singlefileplugin.h" +#include "kerfuffle/kerfuffle_export.h" +#include "kerfuffle/queries.h" + +#include +#include +#include +#include + +#include +#include +#include + +LibSingleFileInterface::LibSingleFileInterface(QObject *parent, const QVariantList & args) + : Kerfuffle::ReadOnlyArchiveInterface(parent, args) +{ +} + +LibSingleFileInterface::~LibSingleFileInterface() +{ +} + +bool LibSingleFileInterface::copyFiles(const QList & files, const QString & destinationDirectory, Kerfuffle::ExtractionOptions options) +{ + Q_UNUSED(files) + Q_UNUSED(options) + + QString outputFileName = destinationDirectory; + if (!destinationDirectory.endsWith(QLatin1Char('/'))) { + outputFileName += QLatin1Char('/'); + } + outputFileName += uncompressedFileName(); + + outputFileName = overwriteFileName(outputFileName); + if (outputFileName.isEmpty()) { + return true; + } + + kDebug() << "Extracting to" << outputFileName; + + QFile outputFile(outputFileName); + if (!outputFile.open(QIODevice::WriteOnly)) { + kDebug() << "Failed to open output file" << outputFile.errorString(); + emit error(i18nc("@info", "Ark could not extract %1.", outputFile.fileName())); + + return false; + } + + QIODevice *device = KFilterDev::deviceForFile(filename(), m_mimeType, false); + if (!device) { + kDebug() << "Could not create KFilterDev"; + emit error(i18nc("@info", "Ark could not open %1 for extraction.", filename())); + + return false; + } + + device->open(QIODevice::ReadOnly); + + qint64 bytesRead; + QByteArray dataChunk(1024*16, '\0'); // 16Kb + + while (true) { + bytesRead = device->read(dataChunk.data(), dataChunk.size()); + + if (bytesRead == -1) { + emit error(i18nc("@info", "There was an error while reading %1 during extraction.", filename())); + break; + } else if (bytesRead == 0) { + break; + } + + outputFile.write(dataChunk.data(), bytesRead); + } + + delete device; + + return true; +} + +bool LibSingleFileInterface::list() +{ + kDebug(); + + const QString filename = uncompressedFileName(); + + Kerfuffle::ArchiveEntry e; + + e[Kerfuffle::FileName] = filename; + e[Kerfuffle::InternalID] = filename; + + emit entry(e); + + return true; +} + +QString LibSingleFileInterface::overwriteFileName(QString& filename) +{ + QString newFileName(filename); + + while (QFile::exists(newFileName)) { + Kerfuffle::OverwriteQuery query(newFileName); + + query.setMultiMode(false); + emit userQuery(&query); + query.waitForResponse(); + + if ((query.responseCancelled()) || (query.responseSkip())) { + return QString(); + } else if (query.responseOverwrite()) { + break; + } else if (query.responseRename()) { + newFileName = query.newFilename(); + } + } + + return newFileName; +} + +const QString LibSingleFileInterface::uncompressedFileName() const +{ + QString uncompressedName(QFileInfo(filename()).fileName()); + + foreach(const QString & extension, m_possibleExtensions) { + kDebug() << extension; + + if (uncompressedName.endsWith(extension, Qt::CaseInsensitive)) { + uncompressedName.chop(extension.size()); + return uncompressedName; + } + } + + return uncompressedName + QLatin1String( ".uncompressed" ); +} + +#include "singlefileplugin.moc" diff --git a/ark/plugins/libsinglefileplugin/singlefileplugin.h b/ark/plugins/libsinglefileplugin/singlefileplugin.h new file mode 100644 index 00000000..1cbce40f --- /dev/null +++ b/ark/plugins/libsinglefileplugin/singlefileplugin.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2009 Raphael Kubo da Costa + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ( INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION ) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef SINGLEFILEPLUGIN_H +#define SINGLEFILEPLUGIN_H + +#include "kerfuffle/archiveinterface.h" + +class LibSingleFileInterface : public Kerfuffle::ReadOnlyArchiveInterface +{ + Q_OBJECT + +public: + LibSingleFileInterface(QObject *parent, const QVariantList & args); + virtual ~LibSingleFileInterface(); + + virtual bool list(); + virtual bool copyFiles(const QList & files, const QString & destinationDirectory, Kerfuffle::ExtractionOptions options); + +protected: + const QString uncompressedFileName() const; + QString overwriteFileName(QString& filename); + + QString m_mimeType; + QStringList m_possibleExtensions; +}; + +#endif // SINGLEFILEPLUGIN_H diff --git a/ark/plugins/libsinglefileplugin/xzplugin.cpp b/ark/plugins/libsinglefileplugin/xzplugin.cpp new file mode 100644 index 00000000..5805dbbd --- /dev/null +++ b/ark/plugins/libsinglefileplugin/xzplugin.cpp @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2009 Raphael Kubo da Costa + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ( INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION ) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "xzplugin.h" +#include "kerfuffle/kerfuffle_export.h" + +#include + +LibXzInterface::LibXzInterface(QObject *parent, const QVariantList & args) + : LibSingleFileInterface(parent, args) +{ + m_mimeType = QLatin1String( "application/x-lzma" ); + m_possibleExtensions.append(QLatin1String( ".lzma" )); + m_possibleExtensions.append(QLatin1String( ".xz" )); +} + +LibXzInterface::~LibXzInterface() +{ +} + +KERFUFFLE_EXPORT_PLUGIN(LibXzInterface) + +#include "xzplugin.moc" diff --git a/ark/plugins/libsinglefileplugin/xzplugin.h b/ark/plugins/libsinglefileplugin/xzplugin.h new file mode 100644 index 00000000..bf5c0e52 --- /dev/null +++ b/ark/plugins/libsinglefileplugin/xzplugin.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2009 Raphael Kubo da Costa + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ( INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION ) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef XZPLUGIN_H +#define XZPLUGIN_H + +#include "singlefileplugin.h" + +class LibXzInterface : public LibSingleFileInterface +{ + Q_OBJECT + +public: + LibXzInterface(QObject *parent, const QVariantList & args); + virtual ~LibXzInterface(); +}; + +#endif // XZPLUGIN_H