1
/* This file is part of Clementine.
2
Copyright 2010, David Sansome <me@davidsansome.com>
4
Clementine is free software: you can redistribute it and/or modify
5
it under the terms of the GNU General Public License as published by
6
the Free Software Foundation, either version 3 of the License, or
7
(at your option) any later version.
9
Clementine is distributed in the hope that it will be useful,
10
but WITHOUT ANY WARRANTY; without even the implied warranty of
11
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
GNU General Public License for more details.
14
You should have received a copy of the GNU General Public License
15
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
19
#include "scriptarchive.h"
20
#include "core/utilities.h"
24
#include <QTemporaryFile>
25
#include <QtConcurrentRun>
27
#ifdef HAVE_LIBARCHIVE
29
# include <archive_entry.h>
32
#ifdef HAVE_LIBARCHIVE
34
// Read callbacks for libarchive
35
struct IODeviceReadState {
40
ssize_t IODeviceRead(struct archive* a, void* client_data, const void** buf) {
41
IODeviceReadState* state = reinterpret_cast<IODeviceReadState*>(client_data);
44
return state->device_->read(state->buf_, sizeof(state->buf_));
47
int IODeviceClose(struct archive* a, void* client_data) {
48
IODeviceReadState* state = reinterpret_cast<IODeviceReadState*>(client_data);
50
state->device_->close();
54
// Utility function to copy an entry to a QIODevice
55
void CopyArchiveEntry(struct archive* in, struct archive* out) {
58
size_t bytes_read = archive_read_data(in, buf, sizeof(buf));
59
if (bytes_read == ARCHIVE_FATAL ||
60
bytes_read == ARCHIVE_WARN ||
61
bytes_read == ARCHIVE_RETRY) {
62
qWarning() << "Error reading archive:" << archive_error_string(in);
65
if (bytes_read == 0) {
69
if (archive_write_data(out, buf, bytes_read) == -1) {
70
qWarning() << "Error extracting archive:" << archive_error_string(out);
76
#endif // HAVE_LIBARCHIVE
78
ScriptArchive::ScriptArchive(ScriptManager* manager)
83
ScriptArchive::~ScriptArchive() {
84
if (!temp_dir_name_.isEmpty() && QFile::exists(temp_dir_name_)) {
85
Utilities::RemoveRecursive(temp_dir_name_);
89
QFuture<bool> ScriptArchive::LoadFromFileAsync(const QString& filename) {
90
return QtConcurrent::run(this, &ScriptArchive::LoadFromFile, filename);
93
QFuture<bool> ScriptArchive::LoadFromDeviceAsync(QIODevice* device) {
94
return QtConcurrent::run(this, &ScriptArchive::LoadFromDevice, device);
97
bool ScriptArchive::LoadFromFile(const QString& filename) {
99
if (!file.open(QIODevice::ReadOnly)) {
103
return LoadFromDevice(&file);
106
bool ScriptArchive::LoadFromDevice(QIODevice* device) {
107
#ifdef HAVE_LIBARCHIVE
108
archive* a = archive_read_new();
109
archive_read_support_compression_gzip(a);
110
archive_read_support_format_tar(a);
112
IODeviceReadState read_state;
113
read_state.device_ = device;
116
if (archive_read_open(a, &read_state, NULL, IODeviceRead, IODeviceClose)) {
117
archive_read_finish(a);
123
// Open a writer to a location in /tmp
124
temp_dir_name_ = Utilities::MakeTempDir();
125
archive* writer = archive_write_disk_new();
126
archive_write_disk_set_options(writer,
127
ARCHIVE_EXTRACT_SECURE_SYMLINKS | ARCHIVE_EXTRACT_SECURE_NODOTDOT);
129
archive_entry* entry;
130
archive_entry* out_entry = archive_entry_new();
131
while (archive_read_next_header(a, &entry) == 0) {
132
// Figure out where we'll extract this file
133
const QString pathname = QString::fromUtf8(archive_entry_pathname(entry));
134
const QString destination = temp_dir_name_ + "/" + pathname;
136
// Copy the header and change the path name
137
archive_entry_clear(out_entry);
138
archive_entry_set_size(out_entry, archive_entry_size(entry));
139
archive_entry_set_filetype(out_entry, archive_entry_filetype(entry));
140
archive_entry_set_mode(out_entry, archive_entry_mode(entry));
141
archive_entry_copy_pathname(out_entry, destination.toLocal8Bit().constData());
144
archive_write_header(writer, out_entry);
145
CopyArchiveEntry(a, writer);
147
// Have we found a script.ini?
148
const QStringList source_parts = pathname.split('/');
150
if (source_parts.count() == 2 && source_parts[1] == ScriptInfo::kIniFileName) {
153
info.InitFromFile(manager_,
154
destination.section('/', -2, -2),
155
destination.section('/', 0, -2),
158
if (info.is_valid()) {
164
archive_entry_free(out_entry);
165
archive_read_finish(a);
167
return !info_.isEmpty();
168
#else // HAVE_LIBARCHIVE
170
#endif // HAVE_LIBARCHIVE
173
bool ScriptArchive::Install() const {
174
// Where should they go?
175
QString destination = Utilities::GetConfigPath(Utilities::Path_Scripts);
177
// Copy each directory
178
foreach (const ScriptInfo& info, info_) {
179
if (!Utilities::CopyRecursive(info.path(), destination))