1 /*
2  * Copyright (C) 2017-2018 Red Hat, Inc.
3  *
4  * Licensed under the GNU Lesser General Public License Version 2.1
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19  */
20 
21 #include "../../utils/bgettext/bgettext-lib.h"
22 #include "../../utils/tinyformat/tinyformat.hpp"
23 
24 #include "CompsEnvironmentItem.hpp"
25 #include "CompsGroupItem.hpp"
26 #include "RPMItem.hpp"
27 #include "Transaction.hpp"
28 #include "TransactionItem.hpp"
29 
30 namespace libdnf {
31 
Transaction(SQLite3Ptr conn)32 swdb_private::Transaction::Transaction(SQLite3Ptr conn)
33   : libdnf::Transaction(conn)
34 {
35 }
36 
37 void
begin()38 swdb_private::Transaction::begin()
39 {
40     if (id != 0) {
41         throw std::runtime_error(_("Transaction has already began!"));
42     }
43     dbInsert();
44     saveItems();
45 }
46 
47 void
finish(TransactionState state)48 swdb_private::Transaction::finish(TransactionState state)
49 {
50     // save states to the database before checking for UNKNOWN state
51     for (auto i : getItems()) {
52         i->saveState();
53     }
54 
55     for (auto i : getItems()) {
56         if (i->getState() == TransactionItemState::UNKNOWN) {
57             throw std::runtime_error(
58                 tfm::format(_("TransactionItem state is not set: %s"), i->getItem()->toStr()));
59         }
60     }
61 
62     setState(state);
63     dbUpdate();
64 }
65 
66 void
dbInsert()67 swdb_private::Transaction::dbInsert()
68 {
69     const char *sql =
70         "INSERT INTO "
71         "  trans ("
72         "    dt_begin, "
73         "    dt_end, "
74         "    rpmdb_version_begin, "
75         "    rpmdb_version_end, "
76         "    releasever, "
77         "    user_id, "
78         "    cmdline, "
79         "    state, "
80         "    comment, "
81         "    id "
82         "  ) "
83         "VALUES "
84         "  (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
85     SQLite3::Statement query(*conn.get(), sql);
86     query.bindv(getDtBegin(),
87                 getDtEnd(),
88                 getRpmdbVersionBegin(),
89                 getRpmdbVersionEnd(),
90                 getReleasever(),
91                 getUserId(),
92                 getCmdline(),
93                 static_cast< int >(getState()),
94                 getComment());
95     if (getId() > 0) {
96         query.bind(9, getId());
97     }
98     query.step();
99     setId(conn->lastInsertRowID());
100 
101     // add used software - has to be added at initialization state
102     if (!softwarePerformedWith.empty()) {
103         sql = R"**(
104             INSERT OR REPLACE INTO
105                 trans_with (
106                     trans_id,
107                     item_id
108                 )
109             VALUES
110                 (?, ?)
111         )**";
112         SQLite3::Statement swQuery(*conn.get(), sql);
113         bool first = true;
114         for (auto software : softwarePerformedWith) {
115             if (!first) {
116                 swQuery.reset();
117             }
118             first = false;
119             // save the item to create a database id
120             software->save();
121             swQuery.bindv(getId(), software->getId());
122             swQuery.step();
123         }
124     }
125 }
126 
127 void
dbUpdate()128 swdb_private::Transaction::dbUpdate()
129 {
130     const char *sql =
131         "UPDATE "
132         "  trans "
133         "SET "
134         "  dt_begin=?, "
135         "  dt_end=?, "
136         "  rpmdb_version_begin=?, "
137         "  rpmdb_version_end=?, "
138         "  releasever=?, "
139         "  user_id=?, "
140         "  cmdline=?, "
141         "  state=?, "
142         "  comment=? "
143         "WHERE "
144         "  id = ?";
145     SQLite3::Statement query(*conn.get(), sql);
146     query.bindv(getDtBegin(),
147                 getDtEnd(),
148                 getRpmdbVersionBegin(),
149                 getRpmdbVersionEnd(),
150                 getReleasever(),
151                 getUserId(),
152                 getCmdline(),
153                 static_cast< int >(getState()),
154                 getComment(),
155                 getId());
156     query.step();
157 }
158 
159 TransactionItemPtr
addItem(std::shared_ptr<Item> item,const std::string & repoid,TransactionItemAction action,TransactionItemReason reason)160 swdb_private::Transaction::addItem(std::shared_ptr< Item > item,
161                                            const std::string &repoid,
162                                            TransactionItemAction action,
163                                            TransactionItemReason reason)
164 {
165     for (auto & i : items) {
166         if (i->getItem()->toStr() != item->toStr()) {
167             continue;
168         }
169         if (i->getRepoid() != repoid) {
170             continue;
171         }
172         if (i->getAction() != action) {
173             continue;
174         }
175         if (reason > i->getReason()) {
176             // use the more significant reason
177             i->setReason(reason);
178         }
179         // don't add duplicates to the list
180         // return an existing transaction item if exists
181         return i;
182     }
183     auto trans_item = std::make_shared< TransactionItem >(this);
184     trans_item->setItem(item);
185     trans_item->setRepoid(repoid);
186     trans_item->setAction(action);
187     trans_item->setReason(reason);
188     items.push_back(trans_item);
189     return trans_item;
190 }
191 
192 void
saveItems()193 swdb_private::Transaction::saveItems()
194 {
195     // TODO: remove all existing items from the database first?
196     for (auto i : items) {
197         i->save();
198     }
199 
200     /* this has to be done in a separate loop to make sure
201      * that all the items already have ID assigned
202      */
203     for (auto i : items) {
204         i->saveReplacedBy();
205     }
206 }
207 
208 /**
209  * Loader for the transaction items.
210  * \return list of transaction items associated with the transaction
211  */
212 std::vector< TransactionItemPtr >
getItems()213 swdb_private::Transaction::getItems()
214 {
215     if (items.empty()) {
216         items = libdnf::Transaction::getItems();
217     }
218     return items;
219 }
220 
221 /**
222  * Append software to softwarePerformedWith list.
223  * Software is saved to the database using save method and therefore
224  * all the software has to be added before transaction is saved.
225  * \param software RPMItem used to perform the transaction
226  */
227 void
addSoftwarePerformedWith(std::shared_ptr<RPMItem> software)228 swdb_private::Transaction::addSoftwarePerformedWith(std::shared_ptr< RPMItem > software)
229 {
230     softwarePerformedWith.insert(software);
231 }
232 
233 /**
234  * Save console output line for current transaction to the database. Transaction has
235  *  to be saved in advance, otherwise an exception will be thrown.
236  * \param fileDescriptor UNIX file descriptor index (1 = stdout, 2 = stderr).
237  * \param line console output content
238  */
239 void
addConsoleOutputLine(int fileDescriptor,const std::string & line)240 swdb_private::Transaction::addConsoleOutputLine(int fileDescriptor, const std::string &line)
241 {
242     if (!getId()) {
243         throw std::runtime_error(_("Can't add console output to unsaved transaction"));
244     }
245 
246     const char *sql = R"**(
247         INSERT INTO
248             console_output (
249                 trans_id,
250                 file_descriptor,
251                 line
252             )
253         VALUES
254             (?, ?, ?);
255     )**";
256     SQLite3::Statement query(*conn, sql);
257     query.bindv(getId(), fileDescriptor, line);
258     query.step();
259 }
260 
261 } // namespace libdnf
262