29 examples
Integer overflow
Integer exceeds the maximum allowed value, leading to incorrect results.
[ FAQ1 ]
What is integer overflow?
Integer overflow occurs when a numeric calculation exceeds the maximum value that a given integer type can store, causing the value to wrap around or produce incorrect results. For example, adding two large signed integers might yield a negative number or an unexpectedly small value due to overflow. This issue arises from the limited bit size allocated to numeric types, such as a 32-bit or 64-bit signed integer, which constrains the range of representable values. Integer overflows can lead to logical errors, security vulnerabilities, and unpredictable application behavior.
[ FAQ2 ]
How to prevent integer overflow
To prevent integer overflow, carefully select integer types large enough to handle anticipated numeric ranges or implement arithmetic checks before performing operations that risk overflow. In languages like C or C++, explicitly check arithmetic operations for overflow conditions, using safer arithmetic libraries or built-in language features designed for safe math operations. Regularly use static analysis tools and runtime checks to detect potential overflow issues proactively. Adopting best practices, like validating input ranges and performing boundary checks, significantly reduces the risk of overflow bugs in software.
diff block
get_config().compact.sync_to_db_interval,
))
.await;
- log::debug!("[COMPACTOR] Running sync cached downsampling offset to db");
+ log::debug!("[COMPACTOR::JOB] Running sync cached downsampling offset to db");
if let Err(e) = crate::service::db::compact::downsampling::sync_cache_to_db().await {
- log::error!("[COMPACTOR] run sync cached downsampling offset to db error: {e}");
+ log::error!("[COMPACTOR::JOB] run sync cached downsampling offset to db error: {e}");
}
}
}
async fn run_check_running_jobs() -> Result<(), anyhow::Error> {
loop {
let time = get_config().compact.job_run_timeout;
- log::debug!("[COMPACTOR] Running check running jobs");
+ log::debug!("[COMPACTOR::JOB] Running check running jobs");
let updated_at = config::utils::time::now_micros() - (time * 1000 * 1000);
greptile
logic: Potential integer overflow if time is large. Consider using checked_mul() for the microseconds calculation.
diff block
return isAddress(address);
}
-export function getHref(chain: Chain, txnHash: string) {
+export function getHref(chain: Chain, hash: string, isBlockHash?: boolean) {
switch (chain) {
case Chain.AVAIL:
- return `${process.env.NEXT_PUBLIC_SUBSCAN_URL}/extrinsic/${txnHash}`
+ return isBlockHash ? `${process.env.NEXT_PUBLIC_SUBSCAN_URL}/block/${hash}` : `${process.env.NEXT_PUBLIC_SUBSCAN_URL}/extrinsic/${hash}`
case Chain.ETH:
- return `${process.env.NEXT_PUBLIC_ETH_EXPLORER_URL}/tx/${txnHash}`
+ return `${process.env.NEXT_PUBLIC_ETH_EXPLORER_URL}/tx/${hash}`
case Chain.BASE:
- return `${process.env.NEXT_PUBLIC_BASE_EXPLORER_URL}/tx/${txnHash}`
+ return `${process.env.NEXT_PUBLIC_BASE_EXPLORER_URL}/tx/${hash}`
default:
throw new Error(`Unsupported chain: ${chain}`)
}
}
+export const fromBridgeHex = (hex?: string) => {
+ if (!hex) return '';
+ return parseInt(hex.toLowerCase().replace('0x', ''), 16).toString();
+};
greptile
logic: parseInt can't handle large numbers safely. Use BigInt for parsing hex to avoid potential integer overflow
```suggestion
export const fromBridgeHex = (hex?: string) => {
if (!hex) return '';
+ return BigInt(`0x${hex.toLowerCase().replace('0x', '')}`).toString();
};
```
diff block
+use std::time::Instant;
+
+use anyhow::Result;
+use async_trait::async_trait;
+use chrono::Utc;
+use diesel::{upsert::excluded, ExpressionMethods, QueryDsl};
+use diesel_async::RunQueryDsl;
+use serde_json::Value;
+use serde::{Deserialize, Serialize};
+use uuid::Uuid;
+use tracing::{debug, error, info, warn};
+
+use crate::{
+ database::{
+ enums::Verification,
+ lib::get_pg_pool,
+ models::{DashboardFile, MetricFile},
+ schema::{dashboard_files, metric_files},
+ },
+ utils::tools::ToolExecutor,
+};
+use super::{
+ file_types::{
+ dashboard_yml::DashboardYml,
+ file::FileEnum,
+ metric_yml::MetricYml,
+ },
+ FileModificationTool,
+};
+
+use litellm::ToolCall;
+
+#[derive(Debug, Serialize, Deserialize, Clone)]
+pub struct Modification {
+ pub new_content: String,
+ pub line_numbers: Vec<i64>,
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+pub struct FileModification {
+ pub id: Uuid,
+ pub file_type: String,
+ pub file_name: String,
+ pub modifications: Vec<Modification>,
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+pub struct ModifyFilesParams {
+ pub files: Vec<FileModification>,
+}
+
+#[derive(Debug, Serialize)]
+struct ModificationResult {
+ file_id: Uuid,
+ file_type: String,
+ file_name: String,
+ success: bool,
+ original_lines: Vec<i64>,
+ adjusted_lines: Vec<i64>,
+ error: Option<String>,
+ modification_type: String,
+ timestamp: chrono::DateTime<Utc>,
+ duration: i64,
+}
+
+#[derive(Debug)]
+struct FileModificationBatch {
+ metric_files: Vec<MetricFile>,
+ dashboard_files: Vec<DashboardFile>,
+ metric_ymls: Vec<MetricYml>,
+ dashboard_ymls: Vec<DashboardYml>,
+ failed_modifications: Vec<(String, String)>,
+ modification_results: Vec<ModificationResult>,
+}
+
+#[derive(Debug)]
+struct LineAdjustment {
+ original_start: i64,
+ original_end: i64,
+ new_length: i64,
+ offset: i64,
+}
+
+impl LineAdjustment {
+ fn new(original_start: i64, original_end: i64, new_length: i64) -> Self {
+ let original_length = original_end - original_start + 1;
+ let offset = new_length - original_length;
+
+ Self {
+ original_start,
+ original_end,
+ new_length,
+ offset,
+ }
+ }
+
+ fn adjust_line_number(&self, line: i64) -> i64 {
+ if line < self.original_start {
+ line
+ } else {
+ line + self.offset
+ }
+ }
+}
+
+// Helper functions for line number validation and modification
+fn validate_line_numbers(line_numbers: &[i64]) -> Result<()> {
+ // Check if empty
+ if line_numbers.is_empty() {
+ return Err(anyhow::anyhow!("Line numbers array cannot be empty"));
+ }
+
+ // Check if we have exactly 2 numbers for the range
+ if line_numbers.len() != 2 {
+ return Err(anyhow::anyhow!(
+ "Line numbers must specify a range with exactly 2 numbers [start,end], got {} numbers",
+ line_numbers.len()
+ ));
+ }
+
+ let start = line_numbers[0];
+ let end = line_numbers[1];
+
+ // Check if starts with at least 1
+ if start < 1 {
+ return Err(anyhow::anyhow!(
+ "Line numbers must be 1-indexed, got starting line {}",
+ start
+ ));
+ }
+
+ // Check if end is greater than or equal to start
+ if end < start {
+ return Err(anyhow::anyhow!(
+ "End line {} must be greater than or equal to start line {}",
+ end,
+ start
+ ));
+ }
+
+ Ok(())
+}
+
+// Helper function to expand range into sequential numbers
+fn expand_line_range(line_numbers: &[i64]) -> Vec<i64> {
+ if line_numbers.len() != 2 {
+ return line_numbers.to_vec();
+ }
+ let start = line_numbers[0];
+ let end = line_numbers[1];
+ (start..=end).collect()
+}
+
+fn apply_modifications_to_content(content: &str, modifications: &[Modification], file_name: &str) -> Result<String> {
+ let mut lines: Vec<&str> = content.lines().collect();
+ let mut modified_lines = lines.clone();
+ let mut total_offset = 0;
+
+ // Validate and sort modifications by starting line number
+ let mut sorted_modifications = modifications.to_vec();
+ sorted_modifications.sort_by_key(|m| m.line_numbers[0]);
+
+ // Check for overlapping modifications
+ for window in sorted_modifications.windows(2) {
+ let first_end = window[0].line_numbers[1];
+ let second_start = window[1].line_numbers[0];
+ if second_start <= first_end {
+ return Err(anyhow::anyhow!(
+ "Overlapping modifications in file '{}': line {} overlaps with line {}",
+ file_name,
+ first_end,
+ second_start
+ ));
+ }
+ }
+
+ // Apply modifications and track adjustments
+ for modification in &sorted_modifications {
+ // Validate line numbers
+ validate_line_numbers(&modification.line_numbers)?;
+
+ // Expand range into sequential numbers for processing
+ let line_range = expand_line_range(&modification.line_numbers);
+
+ // Adjust line numbers based on previous modifications
+ let original_start = line_range[0] as usize - 1;
+ let original_end = line_range[line_range.len() - 1] as usize - 1;
+ let adjusted_start = (original_start as i64 + total_offset) as usize;
greptile
logic: potential integer overflow risk when converting i64 to usize for line indices
suggested fix
+ let original_start = i64::try_from(line_range[0]).and_then(|n| usize::try_from(n.saturating_sub(1))).map_err(|_| anyhow::anyhow!("Line number too large"))?;
+ let original_end = i64::try_from(line_range[line_range.len() - 1]).and_then(|n| usize::try_from(n.saturating_sub(1))).map_err(|_| anyhow::anyhow!("Line number too large"))?;
+ let adjusted_start = usize::try_from(i64::try_from(original_start)? + total_offset).map_err(|_| anyhow::anyhow!("Adjusted line number too large"))?;
diff block
@AllArgsConstructor
public class Scoreboard {
- private final UserTrackerRepository userTrackerRepository;
- private final UserRepository userRepository;
- private final Course course;
- private final PluginMessages pluginMessages;
+ private final UserProgressRepository userTrackerRepository;
+ private final UserRepository userRepository;
+ private final Course course;
+ private final PluginMessages pluginMessages;
- @AllArgsConstructor
- @Getter
- private class Ranking {
- private String username;
- private List<String> flagsCaptured;
- }
+ @AllArgsConstructor
+ @Getter
+ private class Ranking {
+ private String username;
+ private List<String> flagsCaptured;
+ }
- @GetMapping("/scoreboard-data")
- public List<Ranking> getRankings() {
- List<WebGoatUser> allUsers = userRepository.findAll();
- List<Ranking> rankings = new ArrayList<>();
- for (WebGoatUser user : allUsers) {
- if (user.getUsername().startsWith("csrf-")) {
- //the csrf- assignment specific users do not need to be in the overview
- continue;
- }
- UserTracker userTracker = userTrackerRepository.findByUser(user.getUsername());
- rankings.add(new Ranking(user.getUsername(), challengesSolved(userTracker)));
- }
- /* sort on number of captured flags to present an ordered ranking */
- rankings.sort((o1, o2) -> o2.getFlagsCaptured().size() - o1.getFlagsCaptured().size());
- return rankings;
- }
+ @GetMapping("/scoreboard-data")
+ public List<Ranking> getRankings() {
+ return userRepository.findAll().stream()
+ .filter(user -> !user.getUsername().startsWith("csrf-"))
+ .map(
+ user ->
+ new Ranking(
+ user.getUsername(),
+ challengesSolved(userTrackerRepository.findByUser(user.getUsername()))))
+ .sorted((o1, o2) -> o2.getFlagsCaptured().size() - o1.getFlagsCaptured().size())
greptile
style: sorting directly on list size can cause integer overflow for very large lists - consider using Integer.compare()
suggested fix
+ .sorted((o1, o2) -> Integer.compare(o2.getFlagsCaptured().size(), o1.getFlagsCaptured().size()))
diff block
}
impl IoChannel for UringIoChannel {
- fn add_read(&mut self, sector: u64, buf: SharedBuffer, len: usize, id: usize) {
+ fn add_read(&mut self, sector_offset: u64, sector_count: u32, buf: SharedBuffer, id: usize) {
let mut buf = buf.borrow_mut();
let fd = self.file.as_raw_fd();
- let read_e =
- io_uring::opcode::Read::new(io_uring::types::Fd(fd), buf.as_mut_ptr(), len as u32)
- .offset(sector * 512)
- .build()
- .user_data(id as u64);
+ let len = sector_count * SECTOR_SIZE as u32;
greptile
style: potential integer overflow if sector_count * SECTOR_SIZE exceeds u32::MAX
suggested fix
+ let len = sector_count.checked_mul(SECTOR_SIZE as u32).ok_or_else(|| {
+ error!("sector_count * SECTOR_SIZE would overflow u32");
+ Error::InvalidParameter
+ })?;
diff block
+import { withCache } from "@raycast/utils";
+import { callOpenAIChannel, callOpenAIThread } from "./openaiApi";
+import { fetchChannelIdByName, fetchAllUsers, fetchFullThread, fetchThreadsForChannel } from "./slackApi";
+
+let userMap = {};
+
+// ──────────────────────────────────────────────────────────────
+// Shared helpers
+
+// Turn a thread URL | ts → {channelId, threadTs}
+function parseThread(input) {
+ // URL pattern: https://…/archives/C12345/p1714445821123456
+ if (/^https?:\/\//i.test(input)) {
+ const m = input.match(/archives\/([A-Z0-9]+)\/p(\d{16})/);
+ if (!m) throw new Error("Unrecognised Slack thread URL");
+ return { channelId: m[1], threadTs: `${Number(m[2]) / 1000000}` };
+ }
+ // raw ts => we still need channelId
+ throw new Error("Unrecognised Slack thread URL");
+}
+
+async function loadAllUsers() {
+ userMap = await withCache(fetchAllUsers, {
+ maxAge: 60 * 60 * 1000 * 24 * 14, // 14 days in ms
+ })();
+ return userMap;
+}
+
+function getUserName(uid) {
+ if (!uid) return "system";
+ const u = userMap[uid];
+ return u?.profile?.display_name?.trim() || u?.profile?.real_name?.trim() || uid;
+}
+
+function replaceUserMentions(text = "") {
+ return text.replace(/<@(\w+)>/g, (_, id) => `@${getUserName(id)}`);
+}
+
+function buildPromptBody(messages, itemIdx = 1) {
+ // Start the prompt body with a label for the thread, e.g., "Thread 1:"
+ return [
+ `Thread ${itemIdx}:`,
+ // Map over each message in the thread
+ ...messages.map((m) => {
+ // Get the display name or real name of the user who sent the message
+ const name = getUserName(m.user);
+
+ // Clean up the message text: remove newlines and replace Slack user mentions
+ const txt = replaceUserMentions(m.text?.replace(/\n/g, " ") ?? "");
+
+ // Format each message as a bullet point with the username and cleaned message text
+ return `- @${name}: ${txt}`;
+ }),
+
+ // Add a blank line after the thread content
+ "",
+ ].join("\n"); // Join the array into a single string with newline characters
+}
+
+// ──────────────────────────────────────────────────────────────
+
+// Public API
+export async function summarizeChannel(channelName, days = 7, customPrompt) {
+ await loadAllUsers();
+
+ const channelId = await fetchChannelIdByName(channelName);
+
+ const oldestTs = Math.floor(Date.now() / 1000) - days * 24 * 60 * 60;
greptile
logic: Potential integer overflow if days parameter is too large. Add validation for reasonable day ranges.
diff block
+use crate::GUARDIAN_PUBKEY_LENGTH;
+
+/// Guardian set account owned by the Wormhole Core Bridge program.
+///
+/// NOTE: This is a zero-copy struct only meant to read in account data. There
+/// is no verification of whether this account is owned by the Wormhole Verify
+/// VAA program. You must ensure that the account is owned by this program.
+///
+/// Because this account does not have a discriminator, you must also ensure
+/// that the account key matches the PDA's expected key with
+/// [find_guardian_set_address].
+///
+/// [find_guardian_set_address]: crate::find_guardian_set_address
+pub struct GuardianSet<'data>(&'data [u8]);
+
+impl<'data> GuardianSet<'data> {
+ pub const MINIMUM_SIZE: usize = {
+ 4 // guardian set index
+ + 4 // keys length
+ + 4 // creation time
+ + 4 // expiration time
+ };
+
+ /// Attempts to read a guardian set account from the given data. This method
+ /// will return `None` if the data is not a valid guardian set account.
+ pub fn new(data: &'data [u8]) -> Option<Self> {
+ if data.len() < Self::MINIMUM_SIZE {
+ return None;
+ }
+
+ let account = Self(data);
+ let total_len = (account.keys_len() as usize)
+ .checked_mul(GUARDIAN_PUBKEY_LENGTH)?
+ .checked_add(Self::MINIMUM_SIZE)?;
+
+ if data.len() < total_len {
+ return None;
+ }
+
+ Some(account)
+ }
+
+ /// Guardian set index that these keys correspond to.
+ #[inline]
+ pub fn guardian_set_index(&self) -> u32 {
+ u32::from_le_bytes(self.guardian_set_index_slice().try_into().unwrap())
+ }
+
+ /// Number of guardian public keys in this set.
+ #[inline]
+ pub fn keys_len(&self) -> u32 {
+ u32::from_le_bytes(self.keys_len_slice().try_into().unwrap())
+ }
+
+ /// Guardian public key at the given index. This method will return `None`
+ /// if the index is out of bounds.
+ #[inline]
+ pub fn key(&self, index: usize) -> Option<[u8; GUARDIAN_PUBKEY_LENGTH]> {
+ self.key_slice(index)?.try_into().ok()
+ }
+
+ /// When the guardian set was created.
+ #[inline]
+ pub fn creation_time(&self) -> u32 {
+ u32::from_le_bytes(self.creation_time_slice().try_into().unwrap())
+ }
+
+ /// When the guardian set will expire. A value of 0 means that the guardian
+ /// set is the current one.
+ #[inline]
+ pub fn expiration_time(&self) -> u32 {
+ u32::from_le_bytes(self.expiration_time_slice().try_into().unwrap())
+ }
+
+ #[inline(always)]
+ pub fn guardian_set_index_slice(&self) -> &'data [u8] {
+ &self.0[..4]
+ }
+
+ #[inline(always)]
+ pub fn keys_len_slice(&self) -> &'data [u8] {
+ &self.0[4..8]
+ }
+
+ #[inline(always)]
+ pub fn key_slice(&self, index: usize) -> Option<&'data [u8]> {
+ if index >= self.keys_len() as usize {
+ return None;
+ }
+
+ let start_idx = 8 + index * GUARDIAN_PUBKEY_LENGTH;
+ let end_idx = 28 + index * GUARDIAN_PUBKEY_LENGTH;
greptile
logic: Potential integer overflow in index calculations for key_slice. Add checked arithmetic for start_idx and end_idx.
suggested fix
+ let start_idx = 8usize.checked_add(index.checked_mul(GUARDIAN_PUBKEY_LENGTH)?)?;
+ let end_idx = start_idx.checked_add(GUARDIAN_PUBKEY_LENGTH)?;
diff block
s
};
+ if is_mirror_deploy {
+ let truncate_at: usize = payload
+ .char_indices()
+ .nth(MAX_PAYLOAD_SNIPPET_SIZE)
+ .map(|(n, _)| n)
+ .unwrap_or(0);
+ let payload_snippet = &payload[0..truncate_at];
greptile
logic: potential integer overflow if payload is empty - unwrap_or(0) will cause slice panic
suggested fix
if is_mirror_deploy {
let truncate_at: usize = payload
.char_indices()
.nth(MAX_PAYLOAD_SNIPPET_SIZE)
.map(|(n, _)| n)
+ .unwrap_or(payload.len());
let payload_snippet = &payload[0..truncate_at];
diff block
+/*
+ * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/
+ *
+ * Copyright (c) 2002 - 2019 Bruce Mayhew
+ *
+ * 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., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ * Getting Source ==============
+ *
+ * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects.
+ */
+
+package org.owasp.webgoat.lessons.securepasswords;
+
+import static org.owasp.webgoat.container.assignments.AttackResultBuilder.failed;
+import static org.owasp.webgoat.container.assignments.AttackResultBuilder.success;
+
+import com.nulabinc.zxcvbn.Strength;
+import com.nulabinc.zxcvbn.Zxcvbn;
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
+import java.util.Locale;
+import org.owasp.webgoat.container.assignments.AssignmentEndpoint;
+import org.owasp.webgoat.container.assignments.AttackResult;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+public class SecurePasswordsAssignment implements AssignmentEndpoint {
+
+ @PostMapping("SecurePasswords/assignment")
+ @ResponseBody
+ public AttackResult completed(@RequestParam String password) {
+ Zxcvbn zxcvbn = new Zxcvbn();
+ StringBuilder output = new StringBuilder();
+ DecimalFormat df = new DecimalFormat("0", DecimalFormatSymbols.getInstance(Locale.ENGLISH));
+ df.setMaximumFractionDigits(340);
+ Strength strength = zxcvbn.measure(password);
+
+ output.append("<b>Your Password: *******</b></br>");
+ output.append("<b>Length: </b>" + password.length() + "</br>");
+ output.append(
+ "<b>Estimated guesses needed to crack your password: </b>"
+ + df.format(strength.getGuesses())
+ + "</br>");
+ output.append(
+ "<div style=\"float: left;padding-right: 10px;\"><b>Score: </b>"
+ + strength.getScore()
+ + "/4 </div>");
+ if (strength.getScore() <= 1) {
+ output.append(
+ "<div style=\"background-color:red;width: 200px;border-radius: 12px;float:"
+ + " left;\"> </div></br>");
+ } else if (strength.getScore() <= 3) {
+ output.append(
+ "<div style=\"background-color:orange;width: 200px;border-radius: 12px;float:"
+ + " left;\"> </div></br>");
+ } else {
+ output.append(
+ "<div style=\"background-color:green;width: 200px;border-radius: 12px;float:"
+ + " left;\"> </div></br>");
+ }
+ output.append(
+ "<b>Estimated cracking time: </b>"
+ + calculateTime(
+ (long) strength.getCrackTimeSeconds().getOnlineNoThrottling10perSecond())
+ + "</br>");
+ if (strength.getFeedback().getWarning().length() != 0)
+ output.append("<b>Warning: </b>" + strength.getFeedback().getWarning() + "</br>");
+ // possible feedback: https://github.com/dropbox/zxcvbn/blob/master/src/feedback.coffee
+ // maybe ask user to try also weak passwords to see and understand feedback?
+ if (strength.getFeedback().getSuggestions().size() != 0) {
+ output.append("<b>Suggestions:</b></br><ul>");
+ for (String sug : strength.getFeedback().getSuggestions())
+ output.append("<li>" + sug + "</li>");
+ output.append("</ul></br>");
+ }
+ output.append("<b>Score: </b>" + strength.getScore() + "/4 </br>");
+
+ if (strength.getScore() >= 4)
+ return success(this).feedback("securepassword-success").output(output.toString()).build();
+ else return failed(this).feedback("securepassword-failed").output(output.toString()).build();
+ }
+
+ public static String calculateTime(long seconds) {
+ int s = 1;
+ int min = (60 * s);
+ int hr = (60 * min);
+ int d = (24 * hr);
+ int yr = (365 * d);
+
+ long years = seconds / (d) / 365;
+ long days = (seconds % yr) / (d);
+ long hours = (seconds % d) / (hr);
+ long minutes = (seconds % hr) / (min);
+ long sec = (seconds % min * s);
greptile
style: Potential integer overflow if seconds is extremely large. Consider using BigInteger for very large time calculations
diff block
}
log::info!(
- "[trace_id {}] search->storage: stream {}/{}/{}, TantivyCountExec execution time {} ms",
+ "[trace_id {}] search->storage: stream {}/{}/{}, TantivyOptimizeExec execution time {} ms",
query.trace_id,
query.org_id,
query.stream_type,
query.stream_name,
idx_took
);
- let array = vec![Arc::new(Int64Array::from(vec![total_hits as i64])) as Arc<dyn Array>];
+ let array = match idx_optimize_mode {
+ InvertedIndexOptimizeMode::SimpleCount => {
+ vec![Arc::new(Int64Array::from(vec![total_hits as i64])) as Arc<dyn Array>]
+ }
+ InvertedIndexOptimizeMode::SimpleHistogram(min_value, bucket_width, num_buckets) => {
+ create_histogram_arrow_array(
+ &schema,
+ histogram_counts,
+ min_value,
+ bucket_width,
+ num_buckets,
+ )?
+ }
+ _ => unreachable!(),
+ };
RecordBatch::try_new(schema, array).map_err(|e| {
- DataFusionError::Internal(format!("TantivyCountExec create record batch error: {e}",))
+ DataFusionError::Internal(format!(
+ "TantivyOptimizeExec create record batch error: {e}",
+ ))
})
}
+
+/// Creates a RecordBatch containing histogram data with timestamps and counts
+///
+/// Parameters:
+/// - schema: The expected schema for the result
+/// - histogram_counts: Vector of counts for each histogram bucket
+/// - min_value: The minimum timestamp value (start of first bucket)
+/// - bucket_width: Width of each bucket in microseconds
+/// - num_buckets: Expected number of buckets
+fn create_histogram_arrow_array(
+ schema: &SchemaRef,
+ histogram_counts: Vec<u64>,
+ min_value: i64,
+ bucket_width: u64,
+ num_buckets: usize,
+) -> Result<Vec<Arc<dyn arrow::array::Array>>, DataFusionError> {
+ // Validate inputs
+ if bucket_width == 0 {
+ return Err(DataFusionError::Internal(
+ "Histogram bucket width cannot be zero".to_string(),
+ ));
+ }
+
+ // Verify schema has expected structure
+ if schema.fields().len() != 2 {
+ return Err(DataFusionError::Internal(format!(
+ "Expected schema with 2 fields for histogram, got {}",
+ schema.fields().len()
+ )));
+ }
+
+ // Ensure we have the right number of buckets (pad or truncate if necessary)
+ let normalized_counts = if histogram_counts.len() != num_buckets {
+ log::warn!(
+ "Histogram counts length ({}) doesn't match expected buckets ({}), normalizing data",
+ histogram_counts.len(),
+ num_buckets
+ );
+ let mut normalized = histogram_counts;
+ normalized.resize(num_buckets, 0);
+ normalized
+ } else {
+ histogram_counts
+ };
+
+ // Pre-allocate vectors with exact capacity
+ let mut timestamp_values = Vec::with_capacity(num_buckets);
+ let mut count_values = Vec::with_capacity(num_buckets);
+
+ for (i, &count) in normalized_counts.iter().enumerate() {
+ if count > 0 {
+ let bucket_timestamp = min_value + (i as i64 * bucket_width as i64);
+ timestamp_values.push(bucket_timestamp);
+ count_values.push(count as i64);
+ }
+ }
greptile
logic: Potential integer overflow when calculating bucket_timestamp. Need to check that i * bucket_width won't overflow i64 bounds.
suggested fix
for (i, &count) in normalized_counts.iter().enumerate() {
if count > 0 {
+ let bucket_offset = i64::try_from(i)
+ .and_then(|i| i64::try_from(bucket_width).map(|w| i * w))
+ .map_err(|_| DataFusionError::Internal("Bucket timestamp calculation would overflow".to_string()))?;
+ let bucket_timestamp = min_value.checked_add(bucket_offset)
+ .ok_or_else(|| DataFusionError::Internal("Bucket timestamp calculation would overflow".to_string()))?;
timestamp_values.push(bucket_timestamp);
count_values.push(count as i64);
}
}
diff block
);
}
- let mut metadata: Box<UbiMetadata> = unsafe { metadata.assume_init() };
+ let metadata: Box<UbiMetadata> = unsafe { metadata.assume_init() };
if metadata.magic != *UBI_MAGIC {
- info!("Metadata magic mismatch, assuming new device");
- metadata.magic.copy_from_slice(UBI_MAGIC);
- metadata.version_major.copy_from_slice(&[0, 0]);
- metadata.version_minor.copy_from_slice(&[0, 0]);
- metadata.reserved = 0;
- metadata.stripe_headers = [[0; 2]; UBI_MAX_STRIPES];
+ error!("Metadata magic mismatch!");
+ return Err(VhostUserBlockError::MetadataError {
+ description: format!(
+ "Metadata magic mismatch! Expected: {:?}, Found: {:?}",
+ UBI_MAGIC, metadata.magic
+ ),
+ });
}
info!("Metadata loaded successfully");
- metadata
+ Ok(metadata)
}
fn create_stripe_status_vec(
metadata: &Box<UbiMetadata>,
- device_sector_count: u64,
+ source_sector_count: u64,
) -> StripeStatusVec {
let v = metadata
.stripe_headers
.iter()
.map(|header| {
- let stripe_id = u16::from_le_bytes(*header);
- if stripe_id == 0 {
+ if *header == 0 {
StripeStatus::NotFetched
} else {
StripeStatus::Fetched
}
})
.collect::<Vec<StripeStatus>>();
- let stripe_sector_count = STRIPE_SECTOR_COUNT as u64;
- let stripe_count = (device_sector_count + stripe_sector_count - 1) / stripe_sector_count;
+ let stripe_sector_count = 1u64 << metadata.stripe_sector_count_shift;
+ let stripe_count = (source_sector_count + stripe_sector_count - 1) / stripe_sector_count;
greptile
logic: potential integer overflow if source_sector_count + stripe_sector_count > u64::MAX
diff block
// if order by is desc, append new partition response at end
if (order_by.toLowerCase() === "desc") {
- state.data[currentQueryIndex] = [
- ...(state.data[currentQueryIndex] ?? []),
- ...searchRes.data.hits,
- ];
+ const hitsLength = searchRes?.data?.hits?.length;
+
+ // if the hits length is greater than the max hits per panel,
+ // then break the hits and append based on remaining raw size
+ // else append the hits
+ if (
+ (state?.data?.[currentQueryIndex]?.length ?? 0) + hitsLength >
+ MAX_HITS_PER_PANEL
+ ) {
+ const remainingHits = Math.max(
+ MAX_HITS_PER_PANEL -
+ (state?.data?.[currentQueryIndex]?.length ?? 0),
+ 0,
+ );
greptile
logic: Potential integer overflow risk if state.data[currentQueryIndex].length approaches MAX_SAFE_INTEGER
diff block
.build()
)
);
-
Ok((tables, scan_stats))
}
#[tracing::instrument(name = "service:search:grpc:storage:cache_files", skip_all)]
pub async fn cache_files(
trace_id: &str,
- files: &[(&str, i64)],
+ files: &[(&str, i64, bool)],
scan_stats: &mut ScanStats,
file_type: &str,
-) -> Result<file_data::CacheType, Error> {
+) -> Result<(file_data::CacheType, u64, u64), Error> {
// check how many files already cached
let mut cached_files = HashSet::with_capacity(files.len());
- for (file, _) in files.iter() {
+ let (mut cache_hits, mut cache_misses) = (0, 0);
+ for (file, ..) in files.iter() {
if file_data::memory::exist(file).await {
scan_stats.querier_memory_cached_files += 1;
cached_files.insert(file);
+ cache_hits += 1;
} else if file_data::disk::exist(file).await {
scan_stats.querier_disk_cached_files += 1;
cached_files.insert(file);
+ cache_hits += 1;
+ } else {
+ cache_misses += 1;
}
}
let files_num = files.len() as i64;
if files_num == scan_stats.querier_memory_cached_files + scan_stats.querier_disk_cached_files {
// all files are cached
- return Ok(file_data::CacheType::None);
+ return Ok((file_data::CacheType::None, cache_hits, cache_misses));
}
greptile
logic: Potential integer overflow if files_num > i64::MAX. Consider using u64 for counting files.
diff block
-const { dag4 } = require( '@stardust-collective/dag4' );
-const { parseSharedArgs } = require('../shared');
+const { dag4 } = require('@stardust-collective/dag4')
+const { parseSharedArgs, logWorkflow } = require('../shared')
const createConfig = () => {
- const args = process.argv.slice(2);
+ const args = process.argv.slice(2)
- if (args.length < 5) {
- throw new Error(
- "Usage: node script.js <dagl0-port-prefix> <dagl1-port-prefix> <ml0-port-prefix> <cl1-port-prefix> <datal1-port-prefix>"
- );
- }
-
- const sharedArgs = parseSharedArgs(args.slice(0, 5));
- return { ...sharedArgs };
-};
+ if (args.length < 5) {
+ throw new Error(
+ 'Usage: node script.js <dagl0-port-prefix> <dagl1-port-prefix> <ml0-port-prefix> <cl1-port-prefix> <datal1-port-prefix>',
+ )
+ }
+ const sharedArgs = parseSharedArgs(args.slice(0, 5))
+ return { ...sharedArgs }
+}
-const SLEEP_TIME_UNTIL_QUERY = 120 * 1000;
+const SLEEP_TIME_UNTIL_QUERY = 30 * 1000
-const FIRST_WALLET_SEED_PHRASE='drift doll absurd cost upon magic plate often actor decade obscure smooth';
-const SECOND_WALLET_SEED_PHRASE='upper pistol movie hedgehog case exhaust wife injury joke live festival shield';
+const FIRST_WALLET_SEED_PHRASE =
+ 'right off artist rare copy zebra shuffle excite evidence mercy isolate raise'
+const SECOND_WALLET_SEED_PHRASE =
+ 'gauge shell cactus system resemble garlic pioneer theme doll grocery tiger spend'
-const FIRST_WALLET_ADDRESS='DAG4Zd2W2JxL1f1gsHQCoaKrRonPSSHLgcqD7osU';
-const SECOND_WALLET_ADDRESS='DAG6kfTqFxLLPLopHqR43CeQrcvJ5k3eXgYSeELt';
-const THIRD_WALLET_ADDRESS='DAG0DQPuvVThrHnz66S4V6cocrtpg59oesAWyRMb';
+const FIRST_WALLET_ADDRESS = 'DAG0d6yzQqBZTCnq7kB9hL8p4cCiFejfM5m6FBJB'
+const SECOND_WALLET_ADDRESS = 'DAG87hragrbzrEQEz6VC5B7hvtm4wAemS7Zg8KFj'
+const THIRD_WALLET_ADDRESS = 'DAG0DQPuvVThrHnz66S4V6cocrtpg59oesAWyRMb'
-const logMessage = ( message ) => {
- const formattedMessage = {
- message
- };
- console.log( formattedMessage );
-};
+const logMessage = (message) => {
+ logWorkflow.info(message)
+}
-const sleep = ( ms ) => {
- return new Promise( ( resolve ) => setTimeout( resolve, ms ) );
-};
+const sleep = (ms) => {
+ return new Promise((resolve) => setTimeout(resolve, ms))
+}
const batchTransaction = async (
- origin,
- destination
+ origin,
+ destination,
+ amount = 10,
+ fee = 1,
+ num = 100,
) => {
- try {
- const txnsData = [];
- for( let idx = 0; idx < 100; idx++ ) {
- const txnBody = {
- address: destination.address,
- amount: 10,
- fee: 1
- };
-
- txnsData.push( txnBody );
- }
-
- logMessage( 'Starting generation' );
- const generatedTransactions = await origin.generateBatchTransactions( txnsData );
- logMessage( 'Generated' );
-
- logMessage( 'Starting sending' );
- const hashes = await origin.sendBatchTransactions( generatedTransactions );
- logMessage( 'Sent' );
-
- logMessage(
- `Transaction from: ${
- origin.address
- } sent - batch. Generated transaction response body: ${JSON.stringify(
- generatedTransactions
- )}. Post hashes: ${hashes}`
- );
-
- return hashes;
- } catch( e ) {
- throw Error( `Error when sending batch transaction: ${e}` );
+ try {
+ const txnsData = []
+ for (let idx = 0; idx < num; idx++) {
+ const txnBody = {
+ address: destination.address,
+ amount,
+ fee,
+ }
+
+ txnsData.push(txnBody)
}
-};
+
+ const generatedTransactions = await origin.generateBatchTransactions(
+ txnsData,
+ )
+
+ const hashes = await origin.sendBatchTransactions(generatedTransactions)
+
+ logMessage(
+ `DAG transaction from: ${origin.address} sent - batch of ${num}.`,
+ )
+
+ return hashes
+ } catch (e) {
+ throw Error(`Error when sending batch transaction: ${e}`)
+ }
+}
const batchMetagraphTransaction = async (
- metagraphTokenClient,
- origin,
- destination
+ metagraphTokenClient,
+ origin,
+ destination,
+ amount = 10,
+ fee = 1,
+ num = 100,
) => {
- try {
- const txnsData = [];
- for( let idx = 0; idx < 100; idx++ ) {
- const txnBody = {
- address: destination.address,
- amount: 10,
- fee: 1
- };
-
- txnsData.push( txnBody );
- }
-
- logMessage( 'Starting generation' );
- const generatedTransactions =
- await metagraphTokenClient.generateBatchTransactions( txnsData );
-
- logMessage( 'Generated' );
- logMessage( 'Starting sending' );
- const hashes = await metagraphTokenClient.sendBatchTransactions(
- generatedTransactions
- );
- logMessage( 'Sent' );
-
- logMessage(
- `Transaction from: ${
- origin.address
- } sent - batch. Generated transaction response body: ${JSON.stringify(
- generatedTransactions
- )}. Post hashes: ${hashes}`
- );
-
- return hashes;
- } catch( e ) {
- throw Error( `Error when sending batch transaction: ${e}` );
- }
-};
-
-const handleBatchTransactions = async ( origin, destination, networkOptions ) => {
- await origin.connect(
- {
- networkVersion: '2.0',
- l0Url: networkOptions.l0GlobalUrl,
- l1Url: networkOptions.dagL1UrlFirstNode,
- testnet: true
- }
- );
-
- try {
- await batchTransaction(
- origin,
- destination
- );
-
- logMessage( `Waiting ${SLEEP_TIME_UNTIL_QUERY}ms until fetch wallet balances` );
- await sleep( SLEEP_TIME_UNTIL_QUERY );
-
- const originBalance = await origin.getBalance();
- const destinationBalance = await destination.getBalance();
-
- return { originBalance, destinationBalance };
- } catch( error ) {
- const errorMessage = `Error when sending transactions between wallets, message: ${error}`;
- logMessage( errorMessage );
- throw error;
+ try {
+ const txnsData = []
+ for (let idx = 0; idx < num; idx++) {
+ const txnBody = {
+ address: destination.address,
+ amount,
+ fee,
+ }
+
+ txnsData.push(txnBody)
}
-};
-
-const handleMetagraphBatchTransactions = async ( origin, destination, networkOptions ) => {
- await origin.connect(
- {
- networkVersion: '2.0',
- l0Url: networkOptions.l0GlobalUrl,
- l1Url: networkOptions.dagL1UrlFirstNode,
- testnet: true
- }
- );
-
- try {
- const metagraphTokenClient = origin.createMetagraphTokenClient( {
- id: networkOptions.metagraphId,
- l0Url: networkOptions.l0MetagraphUrl,
- l1Url: networkOptions.l1MetagraphUrl,
- testnet: true
- } );
-
- await batchMetagraphTransaction(
- metagraphTokenClient,
- origin,
- destination
- );
-
- logMessage( `Waiting ${SLEEP_TIME_UNTIL_QUERY}ms until fetch wallet balances` );
- await sleep( SLEEP_TIME_UNTIL_QUERY );
-
- const originBalance = await metagraphTokenClient.getBalance();
- const destinationBalance = await metagraphTokenClient.getBalanceFor( destination.address );
-
- return { originBalance, destinationBalance };
- } catch( error ) {
- const errorMessage = `Error when sending transactions between wallets, message: ${error}`;
- logMessage( errorMessage );
- throw error;
- }
-};
-
-const sendDoubleSpendTransaction = async ( networkOptions ) => {
- const lastRef = await dag4.network.getAddressLastAcceptedTransactionRef( FIRST_WALLET_ADDRESS );
-
- const accountFirstNode = dag4.createAccount();
- accountFirstNode.loginSeedPhrase( FIRST_WALLET_SEED_PHRASE );
- await accountFirstNode.connect(
- {
- networkVersion: '2.0',
- l0Url: networkOptions.l0GlobalUrl,
- l1Url: networkOptions.dagL1UrlFirstNode,
- testnet: true
- }
- );
- const firstToSecondTx = await accountFirstNode.generateSignedTransaction(
- SECOND_WALLET_ADDRESS,
- 1000,
- 1,
- lastRef
- );
-
- const accountSecondNode = dag4.createAccount();
- accountSecondNode.loginSeedPhrase( FIRST_WALLET_SEED_PHRASE );
- await accountSecondNode.connect(
- {
- networkVersion: '2.0',
- l0Url: networkOptions.l0GlobalUrl,
- l1Url: networkOptions.dagL1UrlSecondNode,
- testnet: true
- }
- );
- const firstToThirdTx = await accountSecondNode.generateSignedTransaction(
- THIRD_WALLET_ADDRESS,
- 1000,
- 1,
- lastRef
- );
-
- try {
- const [firstToSecondSucceeded, firstToThirdSucceeded] = await Promise.all( [
- dag4.network.postTransaction(firstToSecondTx).catch(e => false).then(v => true),
- dag4.network.postTransaction(firstToThirdTx).catch(e => false).then(v => true)
- ] );
-
- logMessage( `Waiting ${SLEEP_TIME_UNTIL_QUERY}ms until fetch wallet balances` );
- await sleep( SLEEP_TIME_UNTIL_QUERY );
-
- const firstWalletBalance = await accountFirstNode.getBalanceFor( FIRST_WALLET_ADDRESS );
- const secondWalletBalance = await accountFirstNode.getBalanceFor( SECOND_WALLET_ADDRESS );
- const thirdWalletBalance = await accountFirstNode.getBalanceFor( THIRD_WALLET_ADDRESS );
-
- if( firstToThirdSucceeded && secondWalletBalance === 9900 && thirdWalletBalance === 1000 ){
- logMessage( `Amount sent to third wallet ${thirdWalletBalance} and not to second: ${secondWalletBalance}` );
- logMessage( `FirstWalletBalance: ${firstWalletBalance}` );
- logMessage( `SecondWalletBalance: ${secondWalletBalance}` );
- logMessage( `ThirdWalletBalance: ${thirdWalletBalance}` );
- return;
- }
-
- if( firstToSecondSucceeded && secondWalletBalance === 10900 && thirdWalletBalance === 0 ){
- logMessage( `Amount sent to second wallet ${secondWalletBalance} and not to third: ${thirdWalletBalance}` );
- logMessage( `FirstWalletBalance: ${firstWalletBalance}` );
- logMessage( `SecondWalletBalance: ${secondWalletBalance}` );
- logMessage( `ThirdWalletBalance: ${thirdWalletBalance}` );
- return;
- }
-
- throw Error( `Double spend occurred` );
- } catch( error ) {
- const errorMessage = `Error when sending double spend transaction between wallets, message: ${error}`;
- logMessage( errorMessage );
- throw error;
- }
-};
-const assertBalance = async (
- originBalance,
- destinationBalance,
- isInitial
+ const generatedTransactions = await metagraphTokenClient.generateBatchTransactions(
+ txnsData,
+ )
+
+ const hashes = await metagraphTokenClient.sendBatchTransactions(
+ generatedTransactions,
+ )
+
+ logMessage(
+ `L0 token transaction from: ${origin.address} sent - batch of ${num}.`,
+ )
+
+ return hashes
+ } catch (e) {
+ throw Error(`Error when sending batch transaction: ${e}`)
+ }
+}
+
+const handleBatchTransactions = async (
+ networkOptions,
+ origin,
+ destination,
+ amount,
+ fee,
+ txnCount,
) => {
- const expectedOriginBalance = isInitial ? 8900 : 9900;
- const expectedDestinationBalance = isInitial ? 11000 : 9900;
-
- if(
- Number( originBalance ) !== expectedOriginBalance ||
- Number( destinationBalance ) !== expectedDestinationBalance
+ if (networkOptions) {
+ await origin.connect({
+ networkVersion: '2.0',
+ l0Url: networkOptions.l0GlobalUrl,
+ l1Url: networkOptions.dagL1UrlFirstNode,
+ testnet: true,
+ })
+ }
+
+ try {
+ await batchTransaction(origin, destination, amount, fee, txnCount)
+
+ logMessage(`Waiting ${SLEEP_TIME_UNTIL_QUERY} ms to fetch wallet balances`)
+ await sleep(SLEEP_TIME_UNTIL_QUERY)
+
+ const originBalance = await origin.getBalance()
+ const destinationBalance = await destination.getBalance()
+
+ return { originBalance, destinationBalance }
+ } catch (error) {
+ const errorMessage = `Error when sending transactions between wallets, message: ${error}`
+ logMessage(errorMessage)
+ throw error
+ }
+}
+
+const handleMetagraphBatchTransactions = async (
+ networkOptions,
+ origin,
+ destination,
+ amount,
+ fee,
+ txnCount,
+) => {
+ try {
+ await origin.connect({
+ networkVersion: '2.0',
+ l0Url: networkOptions.l0GlobalUrl,
+ l1Url: networkOptions.dagL1UrlFirstNode,
+ testnet: true,
+ })
+
+ const metagraphTokenClient = origin.createMetagraphTokenClient({
+ id: networkOptions.metagraphId,
+ l0Url: networkOptions.l0MetagraphUrl,
+ l1Url: networkOptions.l1MetagraphUrl,
+ testnet: true,
+ })
+
+ await batchMetagraphTransaction(
+ metagraphTokenClient,
+ origin,
+ destination,
+ amount,
+ fee,
+ txnCount,
+ )
+
+ logMessage(`Waiting ${SLEEP_TIME_UNTIL_QUERY} ms to fetch wallet balances`)
+ await sleep(SLEEP_TIME_UNTIL_QUERY)
+
+ const originBalance = await metagraphTokenClient.getBalance()
+ const destinationBalance = await metagraphTokenClient.getBalanceFor(
+ destination.address,
+ )
+
+ return { originBalance, destinationBalance }
+ } catch (error) {
+ const errorMessage = `Error when sending transactions between wallets, message: ${error}`
+ logMessage(errorMessage)
+ throw error
+ }
+}
+
+const doubleSpendTest = async (networkOptions, isMetagraph) => {
+ logMessage(
+ `========= Starting double spend transaction test (${
+ isMetagraph ? 'L0 token' : 'DAG'
+ }) =========`,
+ )
+
+ const sendAmount = 1000
+ const sendFee = 0
+
+ const connectConfig = {
+ networkVersion: '2.0',
+ l0Url: networkOptions.l0GlobalUrl,
+ l1Url: networkOptions.dagL1UrlFirstNode,
+ testnet: true,
+ }
+
+ const accountFirstNode = dag4.createAccount()
+ accountFirstNode.loginSeedPhrase(FIRST_WALLET_SEED_PHRASE)
+ accountFirstNode.connect(connectConfig)
+
+ let sendingClient
+ if (isMetagraph) {
+ sendingClient = accountFirstNode.createMetagraphTokenClient({
+ id: networkOptions.metagraphId,
+ l0Url: networkOptions.l0MetagraphUrl,
+ l1Url: networkOptions.l1MetagraphUrl,
+ })
+ } else {
+ sendingClient = accountFirstNode
+ }
+
+ const lastRef = await sendingClient.network.getAddressLastAcceptedTransactionRef(
+ FIRST_WALLET_ADDRESS,
+ )
+
+ const firstToSecondTx = await accountFirstNode.generateSignedTransaction(
+ SECOND_WALLET_ADDRESS,
+ sendAmount,
+ sendFee,
+ lastRef,
+ )
+
+ const firstToThirdTx = await accountFirstNode.generateSignedTransaction(
+ THIRD_WALLET_ADDRESS,
+ sendAmount,
+ sendFee,
+ lastRef,
+ )
+
+ try {
+ const startBalance1 = await sendingClient.getBalanceFor(
+ FIRST_WALLET_ADDRESS,
+ )
+ const startBalance2 = await sendingClient.getBalanceFor(
+ SECOND_WALLET_ADDRESS,
+ )
+ const startBalance3 = await sendingClient.getBalanceFor(
+ THIRD_WALLET_ADDRESS,
+ )
+
+ logMessage('Sending txns w/same lastRef')
+ const [firstToSecondSucceeded, firstToThirdSucceeded] = await Promise.all([
+ sendingClient.network
+ .postTransaction(firstToSecondTx)
+ .then((v) => true)
+ .catch((e) => false),
+ sendingClient.network
+ .postTransaction(firstToThirdTx)
+ .then((v) => true)
+ .catch((e) => false),
+ ])
+
+ logMessage(
+ `Waiting ${SLEEP_TIME_UNTIL_QUERY}ms until fetch wallet balances`,
+ )
+ await sleep(SLEEP_TIME_UNTIL_QUERY)
+
+ const balance1 = await sendingClient.getBalanceFor(FIRST_WALLET_ADDRESS)
+ const balance2 = await sendingClient.getBalanceFor(SECOND_WALLET_ADDRESS)
+ const balance3 = await sendingClient.getBalanceFor(THIRD_WALLET_ADDRESS)
+
+ logMessage(`FirstWalletBalance: ${balance1}`)
+ logMessage(`SecondWalletBalance: ${balance2}`)
+ logMessage(`ThirdWalletBalance: ${balance3}`)
+ logMessage(`firstToSecondSucceeded: ${firstToSecondSucceeded}`)
+ logMessage(`firstToThirdSucceeded: ${firstToThirdSucceeded}`)
+
+ if (
+ firstToSecondSucceeded &&
+ balance1 === startBalance1 - sendAmount - sendFee &&
+ balance2 === startBalance2 + sendAmount &&
+ balance3 === startBalance3
) {
- throw Error( `
- Error sending transactions. Wallet balances are different than expected:
- expectedOriginBalance: ${expectedOriginBalance} ---- originBalance: ${originBalance}
- expectedDestinationBalance: ${expectedDestinationBalance} ---- destinationBalance: ${destinationBalance}
- ` );
+ logMessage(`No double spend: Amount sent to second wallet`)
+ return
}
- logMessage( `Origin Balance: expected ${expectedOriginBalance} ---- actual: ${originBalance}` );
- logMessage( `Destination Balance: expected ${expectedDestinationBalance} ---- actual: ${destinationBalance}` );
-};
-
-const sendTransactionsUsingUrls = async (
- metagraphId,
- l0GlobalUrl,
- dagL1UrlFirstNode,
- dagL1UrlSecondNode,
- l0MetagraphUrl,
- l1MetagraphUrl
-) => {
- const account1 = dag4.createAccount();
- account1.loginSeedPhrase( FIRST_WALLET_SEED_PHRASE );
-
- const account2 = dag4.createAccount();
- account2.loginSeedPhrase( SECOND_WALLET_SEED_PHRASE );
-
- const networkOptions = {
- metagraphId,
- l0GlobalUrl,
- dagL1UrlFirstNode,
- dagL1UrlSecondNode,
- l0MetagraphUrl,
- l1MetagraphUrl
- };
-
- try {
- logMessage( `Starting batch DAG Transactions from: ${account1.address} to ${account2.address}` );
- const { originBalance, destinationBalance } = await handleBatchTransactions( account1, account2, networkOptions );
-
- await assertBalance( originBalance, destinationBalance, true );
-
- logMessage( `Finished batch DAG Transactions from: ${account1.address} to ${account2.address}` );
- } catch( error ) {
- logMessage( `Error sending forth transactions from: ${account1.address} to ${account2.address}:`, error );
- throw error;
+ if (
+ firstToThirdSucceeded &&
+ balance1 === startBalance1 - sendAmount - sendFee &&
+ balance2 === startBalance2 &&
+ balance3 === startBalance3 + sendAmount
+ ) {
+ logMessage(`No double spend: Amount sent to third wallet`)
+ return
}
- try {
- logMessage( `Starting batch DAG Transactions from: ${account2.address} to ${account1.address}` );
- const { originBalance, destinationBalance } = await handleBatchTransactions( account2, account1, networkOptions );
-
- await assertBalance( originBalance, destinationBalance, false );
+ throw Error(`Double spend occurred`)
+ } catch (error) {
+ const errorMessage = `Error when sending double spend transaction between wallets, message: ${error}`
+ logMessage(errorMessage)
+ throw error
+ }
+}
+
+const assertBalances = async (
+ account1Balance,
+ account2Balance,
+ expectedAccount1Balance,
+ expectedAccount2Balance,
+) => {
+ if (
+ Number(account1Balance) !== Number(expectedAccount1Balance) ||
+ Number(account2Balance) !== Number(expectedAccount2Balance)
+ ) {
+ throw Error(`
+ Error sending transactions. Wallet balances are different than expected:
+ expectedAccount1Balance: ${expectedAccount1Balance} ---- actual: ${account1Balance}
+ expectedAccount2Balance: ${expectedAccount2Balance} ---- actual: ${account2Balance}
+ `)
+ }
+
+ logMessage(`Correct Account 1 Balance: ${expectedAccount1Balance}`)
+ logMessage(`Correct Account 2 Balance: ${expectedAccount2Balance}`)
+}
+
+const transferTest = async (
+ fromAccount,
+ toAccount,
+ amount,
+ fee,
+ txnCount,
+ metagraphOpts,
+) => {
+ let fromAccountStart, toAccountStart, isMetagraph
+ if (metagraphOpts) {
+ isMetagraph = true
+
+ const metagraphTokenClient = fromAccount.createMetagraphTokenClient({
+ id: metagraphOpts.metagraphId,
+ l0Url: metagraphOpts.l0MetagraphUrl,
+ l1Url: metagraphOpts.l1MetagraphUrl,
+ })
+
+ fromAccountStart = await metagraphTokenClient.getBalance()
+ toAccountStart = await metagraphTokenClient.getBalanceFor(toAccount.address)
+ } else {
+ fromAccountStart = await fromAccount.getBalance()
+ toAccountStart = await toAccount.getBalance()
+ }
+
+ logMessage(
+ `========= Transfer test (${isMetagraph ? 'L0 token' : 'DAG'}): ${
+ fromAccount.address
+ } to ${toAccount.address} w/fee (${fee}) and count (${txnCount}) =========`,
+ )
+
+ const batchFunc = metagraphOpts
+ ? handleMetagraphBatchTransactions
+ : handleBatchTransactions
+
+ const { originBalance, destinationBalance } = await batchFunc(
+ metagraphOpts,
+ fromAccount,
+ toAccount,
+ amount,
+ fee,
+ txnCount,
+ )
+
+ const totalAmount = txnCount * amount
+ const totalFee = txnCount * fee
+
+ const expectedFromBalance = fromAccountStart - totalAmount - totalFee
+ const expectedToBalance = toAccountStart + totalAmount
greptile
logic: potential integer overflow if txnCount * amount or txnCount * fee exceeds Number.MAX_SAFE_INTEGER
diff block
+use arrow::{array::{Array, ArrayRef, BooleanArray, Float64Array, Int32Array, RecordBatch, StructArray, TimestampNanosecondArray, UInt16Array, UInt32Array, UInt8Array}, datatypes::{DataType, Field, Fields}};
+use arrow_schema::TimeUnit;
+use opentelemetry_proto::tonic::{collector::logs::v1::ExportLogsServiceRequest, common::v1::InstrumentationScope};
+use snafu::OptionExt;
+use arrow::datatypes::DataType::UInt32;
+use crate::otap_adaptor::wrapper::{ByteArrayAccessor, Int64ArrayAccessor, NullableArrayAccessor, StringArrayAccessor};
+use crate::otap_adaptor::error;
+
+pub struct ResourceArrays<'a> {
+ id: &'a UInt16Array,
+ dropped_attributes_count: Option<&'a UInt32Array>,
+ schema_url: Option<StringArrayAccessor<'a>>,
+}
+
+impl ResourceArrays<'_> {
+ fn data_type() -> DataType {
+ DataType::Struct(Fields::from(vec![
+ Field::new("id", DataType::UInt16, true),
+ Field::new("dropped_attributes_count", DataType::UInt32, true),
+ Field::new(
+ "schema_url",
+ DataType::Dictionary(Box::new(DataType::UInt8), Box::new(DataType::Utf8)),
+ true,
+ ),
+ ]))
+ }
+}
+pub struct Downcaster<S, F> {
+ name: &'static str,
+ source: S,
+ array: F,
+ expect_type: fn() -> DataType,
+}
+
+impl<'a, S, F> Downcaster<S, F> {
+ fn downcast<'s, A>(self) -> error::Result<&'a A>
+ where
+ A: Array + 'static,
+ F: Fn(S) -> Option<&'a ArrayRef>,
+ S: 'a,
+ {
+ let array =
+ (self.array)(self.source).context(error::ColumnNotFoundSnafu { name: self.name })?;
+ array
+ .as_any()
+ .downcast_ref::<A>()
+ .with_context(|| error::ColumnDataTypeMismatchSnafu {
+ name: self.name,
+ expect: (self.expect_type)(),
+ actual: array.data_type().clone(),
+ })
+ }
+}
+
+impl<'a> TryFrom<&'a RecordBatch> for ResourceArrays<'a> {
+ type Error = error::Error;
+
+ fn try_from(rb: &'a RecordBatch) -> Result<Self, Self::Error> {
+ let resource_array = Downcaster {
+ name: "resource",
+ source: rb,
+ array: |rb: &'a RecordBatch| rb.column_by_name("resource"),
+ expect_type: Self::data_type,
+ }
+ .downcast::<StructArray>()?;
+
+ let id = Downcaster {
+ name: "id",
+ source: resource_array,
+ array: |s: &'a StructArray| s.column_by_name("id"),
+ expect_type: || DataType::UInt16,
+ }
+ .downcast::<UInt16Array>()?;
+
+ let dropped_attributes_count = resource_array
+ .column_by_name("dropped_attributes_count")
+ .map(|a| {
+ a.as_any().downcast_ref::<UInt32Array>().context(
+ error::ColumnDataTypeMismatchSnafu {
+ name: "dropped_attributes_count",
+ expect: UInt32,
+ actual: a.data_type().clone(),
+ },
+ )
+ })
+ .transpose()?;
+
+ let schema_url = resource_array
+ .column_by_name("schema_url")
+ .map(StringArrayAccessor::try_new)
+ .transpose()?;
+
+ Ok(Self {
+ id,
+ dropped_attributes_count,
+ schema_url,
+ })
+
+ }
+}
+
+
+pub struct ScopeArrays<'a> {
+ name: Option<StringArrayAccessor<'a>>,
+ version: Option<StringArrayAccessor<'a>>,
+ dropped_attributes_count: Option<&'a UInt32Array>,
+ id: Option<&'a UInt16Array>,
+}
+
+impl ScopeArrays<'_> {
+ fn data_type() -> DataType {
+ DataType::Struct(Fields::from(vec![
+ Field::new(
+ "name",
+ DataType::Dictionary(Box::new(DataType::UInt8), Box::new(DataType::Utf8)),
+ true,
+ ),
+ Field::new("version", DataType::Utf8, true),
+ Field::new("dropped_attributes_count", DataType::UInt32, true),
+ Field::new("id", DataType::UInt16, true),
+ ]))
+ }
+}
+
+impl<'a> TryFrom<&'a RecordBatch> for ScopeArrays<'a> {
+ type Error = error::Error;
+
+ fn try_from(rb: &'a RecordBatch) -> Result<Self, Self::Error> {
+ let scope_array = Downcaster {
+ name: "scope",
+ source: rb,
+ array: |rb: &'a RecordBatch| rb.column_by_name("scope"),
+ expect_type: Self::data_type,
+ }
+ .downcast::<StructArray>()?;
+
+ let name = scope_array
+ .column_by_name("name")
+ .map(StringArrayAccessor::try_new)
+ .transpose()?;
+
+ let version = scope_array
+ .column_by_name("version")
+ .map(StringArrayAccessor::try_new)
+ .transpose()?;
+
+ let dropped_attributes_count = scope_array
+ .column_by_name("dropped_attributes_count")
+ .map(|a| {
+ a.as_any().downcast_ref::<UInt32Array>().context(
+ error::ColumnDataTypeMismatchSnafu {
+ name: "dropped_attributes_count",
+ expect: UInt32,
+ actual: a.data_type().clone(),
+ },
+ )
+ })
+ .transpose()?;
+
+ let id = scope_array
+ .column_by_name("id")
+ .map(|a| {
+ a.as_any().downcast_ref::<UInt16Array>().with_context(|| {
+ error::ColumnDataTypeMismatchSnafu {
+ name: "id",
+ expect: DataType::UInt16,
+ actual: a.data_type().clone(),
+ }
+ })
+ })
+ .transpose()?;
+
+ Ok(Self {
+ name,
+ version,
+ dropped_attributes_count,
+ id,
+ })
+ }
+}
+
+//FIXME: remove this when body deserialization is figured out
+#[allow(dead_code)]
+pub struct LogArrays<'a> {
+ id: Option<&'a UInt16Array>,
+ schema_url: Option<StringArrayAccessor<'a>>,
+ time_unix_nano: TimestampNanosecondArray,
+ observed_time_unix_nano: TimestampNanosecondArray,
+ trace_id: Option<ByteArrayAccessor<'a>>,
+ span_id: Option<ByteArrayAccessor<'a>>,
+ severity_number: Option<&'a Int32Array>,
+ severity_text: Option<StringArrayAccessor<'a>>,
+ //TODO: Shall body be a new struct? Is the body called attributes in LogRecord?
+
+ body_type: UInt8Array,
+ body_str: StringArrayAccessor<'a>,
+ body_int: Option<Int64ArrayAccessor<'a>>,
+ body_double: Option<&'a Float64Array>,
+ body_bool: Option<&'a BooleanArray>,
+ body_bytes: Option<ByteArrayAccessor<'a>>,
+ body_ser: Option<ByteArrayAccessor<'a>>,
+ dropped_attributes_count: Option<&'a UInt32Array>,
+ flags: Option<&'a UInt32Array>,
+}
+
+impl<'a> TryFrom<&'a RecordBatch> for LogArrays<'a> {
+ type Error = error::Error;
+
+ fn try_from(rb: &'a RecordBatch) -> Result<Self, Self::Error> {
+ let id = rb.column_by_name("id")
+ .map(|arr|{
+ arr.as_any()
+ .downcast_ref::<UInt16Array>()
+ .with_context(|| error::ColumnDataTypeMismatchSnafu{
+ name: "id",
+ expect: DataType::UInt16,
+ actual: arr.data_type().clone()
+ })
+ }).transpose()?;
+
+ let schema_url = rb.column_by_name("schema_url")
+ .map(StringArrayAccessor::try_new)
+ .transpose()?;
+
+ let time_unix_nano = rb.column_by_name("time_unix_nano")
+ .map(|arr|{
+ arr.as_any()
+ .downcast_ref::<TimestampNanosecondArray>()
+ .with_context(|| error::ColumnDataTypeMismatchSnafu{
+ name: "time_unix_nano",
+ expect: DataType::Timestamp(TimeUnit::Nanosecond, None),
+ actual: arr.data_type().clone()
+ })
+ })
+ .transpose()?
+ .with_context(|| error::ColumnNotFoundSnafu{
+ name: "time_unix_nano"
+ })?.clone();
+
+ let observed_time_unix_nano = rb.column_by_name("observed_time_unix_nano")
+ .map(|arr|{
+ arr.as_any()
+ .downcast_ref::<TimestampNanosecondArray>()
+ .with_context(|| error::ColumnDataTypeMismatchSnafu{
+ name: "observed_time_unix_nano",
+ expect: DataType::Timestamp(TimeUnit::Nanosecond, None),
+ actual: arr.data_type().clone()
+ })
+ })
+ .transpose()?
+ .with_context(|| error::ColumnNotFoundSnafu{
+ name: "observed_time_unix_nano"
+ })?.clone();
+
+ let trace_id = rb.column_by_name("trace_id")
+ .map(ByteArrayAccessor::try_new)
+ .transpose()?;
+
+ let span_id = rb.column_by_name("span_id")
+ .map(ByteArrayAccessor::try_new)
+ .transpose()?;
+
+ let severity_number = rb.column_by_name("severity_number")
+ .map(|arr|{
+ arr.as_any()
+ .downcast_ref::<Int32Array>()
+ .with_context(|| error::ColumnDataTypeMismatchSnafu{
+ name: "severity_number",
+ expect: DataType::Int32,
+ actual: arr.data_type().clone()
+ })
+ }).transpose()?;
+
+ let severity_text = rb.column_by_name("severity_text")
+ .map(StringArrayAccessor::try_new)
+ .transpose()?;
+
+ let body_type = rb.column_by_name("body_type")
+ .map(|arr|{
+ arr.as_any()
+ .downcast_ref::<UInt8Array>()
+ .with_context(|| error::ColumnDataTypeMismatchSnafu{
+ name: "body_type",
+ expect: DataType::UInt8,
+ actual: arr.data_type().clone()
+ })
+ }).transpose()?
+ .with_context(|| error::ColumnNotFoundSnafu{
+ name: "body_type"
+ })?.clone();
+
+ let body_str = rb.column_by_name("body_str")
+ .map(StringArrayAccessor::try_new)
+ .transpose()?
+ .with_context(|| error::ColumnNotFoundSnafu{
+ name: "body_str"
+ })?;
+
+ let body_int = rb.column_by_name("body_int")
+ .map(Int64ArrayAccessor::try_new).transpose()?;
+
+ let body_double = rb.column_by_name("body_double")
+ .map(|arr|{
+ arr.as_any()
+ .downcast_ref::<Float64Array>()
+ .with_context(|| error::ColumnDataTypeMismatchSnafu{
+ name: "body_double",
+ expect: DataType::Float64,
+ actual: arr.data_type().clone()
+ })
+ }).transpose()?;
+
+ let body_bool = rb.column_by_name("body_bool")
+ .map(|arr|{
+ arr.as_any()
+ .downcast_ref::<BooleanArray>()
+ .with_context(|| error::ColumnDataTypeMismatchSnafu{
+ name: "body_bool",
+ expect: DataType::Boolean,
+ actual: arr.data_type().clone()
+ })
+ })
+ .transpose()?;
+
+ let body_bytes = rb.column_by_name("body_bytes")
+ .map(ByteArrayAccessor::try_new)
+ .transpose()?;
+ let body_ser = rb.column_by_name("body_ser")
+ .map(ByteArrayAccessor::try_new)
+ .transpose()?;
+
+ let dropped_attributes_count = rb
+ .column_by_name("dropped_attributes_count")
+ .map(|a| {
+ a.as_any().downcast_ref::<UInt32Array>().context(
+ error::ColumnDataTypeMismatchSnafu {
+ name: "dropped_attributes_count",
+ expect: UInt32,
+ actual: a.data_type().clone(),
+ },
+ )
+ })
+ .transpose()?;
+ let flags = rb
+ .column_by_name("flags")
+ .map(|a| {
+ a.as_any().downcast_ref::<UInt32Array>().context(
+ error::ColumnDataTypeMismatchSnafu {
+ name: "flags",
+ expect: UInt32,
+ actual: a.data_type().clone(),
+ },
+ )
+ })
+ .transpose()?;
+
+ Ok(Self{
+ id,
+ schema_url,
+ time_unix_nano,
+ observed_time_unix_nano,
+ trace_id,
+ span_id,
+ severity_number,
+ severity_text,
+ body_type,
+ body_str,
+ body_int,
+ body_double,
+ body_bool,
+ body_bytes,
+ body_ser,
+ dropped_attributes_count,
+ flags,
+ })
+ }
+}
+
+pub fn logs_from_rb(
+ rb: &RecordBatch,
+) -> error::Result<ExportLogsServiceRequest> {
+ let mut logs = ExportLogsServiceRequest::default();
+
+ let mut prev_res_id: Option<u16> = None;
+ let mut prev_scope_id: Option<u16> = None;
+
+ let mut res_id = 0;
+ let mut scope_id = 0;
+
+ let resource_arrays = ResourceArrays::try_from(rb)?;
+ let scope_arrays = ScopeArrays::try_from(rb)?;
+ let log_arrays = LogArrays::try_from(rb)?;
+
+
+ for idx in 0..rb.num_rows() {
+ let res_delta_id = resource_arrays.id.value_at(idx).unwrap_or_default();
+ res_id += res_delta_id;
greptile
style: potential integer overflow risk when adding res_delta_id to res_id
suggested fix
let res_delta_id = resource_arrays.id.value_at(idx).unwrap_or_default();
+ res_id = res_id.checked_add(res_delta_id).ok_or(error::Error::InvalidData { message: "Resource ID overflow".to_string() })?;
diff block
+use borsh::{to_vec, BorshDeserialize};
+use solana_program::hash::Hash;
+use solana_program::log::sol_log_64;
+use solana_program::program::invoke_signed;
+use solana_program::program_error::ProgramError;
+use solana_program::sysvar::Sysvar;
+use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult};
+use solana_program::{msg, system_instruction};
+use solana_pubkey::Pubkey;
+
+use crate::error::CommittorError;
+use crate::instruction::CommittorInstruction;
+use crate::utils::{
+ assert_account_unallocated, assert_is_signer, assert_program_id,
+ close_and_refund_authority,
+};
+use crate::{consts, verified_seeds_and_pda, Chunks};
+
+pub fn process(
+ program_id: &Pubkey,
+ accounts: &[AccountInfo],
+ instruction_data: &[u8],
+) -> ProgramResult {
+ assert_program_id(program_id)?;
+
+ let ix = CommittorInstruction::try_from_slice(instruction_data)?;
+ use CommittorInstruction::*;
+ match ix {
+ Init {
+ pubkey,
+ chunks_account_size,
+ buffer_account_size,
+ blockhash,
+ chunks_bump,
+ buffer_bump,
+ chunk_count,
+ chunk_size,
+ } => process_init(
+ program_id,
+ accounts,
+ &pubkey,
+ chunks_account_size,
+ buffer_account_size,
+ blockhash,
+ chunks_bump,
+ buffer_bump,
+ chunk_count,
+ chunk_size,
+ ),
+ ReallocBuffer {
+ pubkey,
+ buffer_account_size,
+ blockhash,
+ buffer_bump,
+ invocation_count,
+ } => process_realloc_buffer(
+ accounts,
+ &pubkey,
+ buffer_account_size,
+ blockhash,
+ buffer_bump,
+ invocation_count,
+ ),
+ Write {
+ pubkey,
+ blockhash,
+ chunks_bump,
+ buffer_bump,
+ offset,
+ data_chunk,
+ } => process_write(
+ accounts,
+ &pubkey,
+ offset,
+ data_chunk,
+ blockhash,
+ chunks_bump,
+ buffer_bump,
+ ),
+ Close {
+ pubkey,
+ blockhash,
+ chunks_bump,
+ buffer_bump,
+ } => process_close(
+ accounts,
+ &pubkey,
+ blockhash,
+ chunks_bump,
+ buffer_bump,
+ ),
+ }
+}
+
+// -----------------
+// process_init
+// -----------------
+#[allow(clippy::too_many_arguments)] // private + only call site is close
+fn process_init(
+ program_id: &Pubkey,
+ accounts: &[AccountInfo],
+ pubkey: &Pubkey,
+ chunks_account_size: u64,
+ buffer_account_size: u64,
+ blockhash: Hash,
+ chunks_bump: u8,
+ buffer_bump: u8,
+ chunk_count: usize,
+ chunk_size: u16,
+) -> ProgramResult {
+ msg!("Instruction: Init");
+
+ let [authority_info, chunks_account_info, buffer_account_info, _system_program] =
+ accounts
+ else {
+ msg!("Need the following accounts: [authority, chunks, buffer, system program ], but got {}", accounts.len());
+ return Err(ProgramError::NotEnoughAccountKeys);
+ };
+ assert_is_signer(authority_info, "authority")?;
+
+ let chunks_bump = &[chunks_bump];
+ let (chunks_seeds, _chunks_pda) = verified_seeds_and_pda!(
+ chunks,
+ authority_info,
+ pubkey,
+ chunks_account_info,
+ blockhash,
+ chunks_bump
+ );
+
+ let buffer_bump = &[buffer_bump];
+ let (buffer_seeds, _buffer_pda) = verified_seeds_and_pda!(
+ buffer,
+ authority_info,
+ pubkey,
+ buffer_account_info,
+ blockhash,
+ buffer_bump
+ );
+
+ assert_account_unallocated(chunks_account_info, "chunks")?;
+ assert_account_unallocated(buffer_account_info, "buffer")?;
+
+ msg!("Creating Chunks and Buffer accounts");
+
+ // Create Chunks Account
+ let ix = system_instruction::create_account(
+ authority_info.key,
+ chunks_account_info.key,
+ solana_program::rent::Rent::get()?
+ .minimum_balance(chunks_account_size as usize),
+ chunks_account_size,
+ program_id,
+ );
+ invoke_signed(
+ &ix,
+ &[authority_info.clone(), chunks_account_info.clone()],
+ &[&chunks_seeds],
+ )?;
+
+ let initial_alloc_size = std::cmp::min(
+ buffer_account_size,
+ consts::MAX_ACOUNT_ALLOC_PER_INSTRUCTION_SIZE as u64,
+ );
+
+ // Create Buffer Account
+ let ix = system_instruction::create_account(
+ authority_info.key,
+ buffer_account_info.key,
+ // NOTE: we fund for the full size to allow realloc without funding more
+ solana_program::rent::Rent::get()?
+ .minimum_balance(buffer_account_size as usize),
+ initial_alloc_size,
+ program_id,
+ );
+ invoke_signed(
+ &ix,
+ &[authority_info.clone(), buffer_account_info.clone()],
+ &[&buffer_seeds],
+ )?;
+
+ msg!(
+ "Initialized and allocated {} of desired {} bytes.",
+ initial_alloc_size,
+ buffer_account_size,
+ );
+
+ // Initialize Chunks Account
+ let chunks = Chunks::new(chunk_count, chunk_size);
+ chunks_account_info
+ .data
+ .borrow_mut()
+ .copy_from_slice(&to_vec(&chunks)?);
+
+ Ok(())
+}
+
+// -----------------
+// process_realloc_buffer
+// -----------------
+fn process_realloc_buffer(
+ accounts: &[AccountInfo],
+ pubkey: &Pubkey,
+ buffer_account_size: u64,
+ blockhash: Hash,
+ buffer_bump: u8,
+ invocation_count: u16,
+) -> ProgramResult {
+ msg!("Instruction: ReallocBuffer {}", invocation_count);
+
+ let [authority_info, buffer_account_info] = accounts else {
+ msg!(
+ "Need the following accounts: [authority, buffer ], but got {}",
+ accounts.len()
+ );
+ return Err(ProgramError::NotEnoughAccountKeys);
+ };
+
+ if buffer_account_info.data.borrow().len() >= buffer_account_size as usize {
+ msg!(
+ "Buffer account already has {} bytes, no need to realloc",
+ buffer_account_info.data.borrow().len()
+ );
+ return Ok(());
+ }
+
+ assert_is_signer(authority_info, "authority")?;
+
+ let buffer_bump = &[buffer_bump];
+ verified_seeds_and_pda!(
+ buffer,
+ authority_info,
+ pubkey,
+ buffer_account_info,
+ blockhash,
+ buffer_bump
+ );
+
+ let current_buffer_size = buffer_account_info.data.borrow().len() as u64;
+ let next_alloc_size = std::cmp::min(
+ buffer_account_size,
+ current_buffer_size
+ + consts::MAX_ACOUNT_ALLOC_PER_INSTRUCTION_SIZE as u64,
+ );
+
+ msg!(
+ "Allocating from {} to {} of desired {} bytes.",
+ current_buffer_size,
+ next_alloc_size,
+ buffer_account_size,
+ );
+
+ // NOTE: we fund the account for the full desired account size during init
+ // Doing this as needed increases the cost for each realloc to 4,959 CUs.
+ // Reallocing without any rent check/increase uses only 4,025 CUs
+ // and does not require the system program to be provided.
+ buffer_account_info.realloc(next_alloc_size as usize, true)?;
+
+ Ok(())
+}
+
+// -----------------
+// process_write
+// -----------------
+fn process_write(
+ accounts: &[AccountInfo],
+ pubkey: &Pubkey,
+ offset: u32,
+ data_chunk: Vec<u8>,
+ blockhash: Hash,
+ chunks_bump: u8,
+ buffer_bump: u8,
+) -> ProgramResult {
+ msg!("Instruction: Write");
+
+ let [authority_info, chunks_account_info, buffer_account_info] = accounts
+ else {
+ msg!("Need the following accounts: [authority, chunks, buffer ], but got {}", accounts.len());
+ return Err(ProgramError::NotEnoughAccountKeys);
+ };
+ assert_is_signer(authority_info, "authority")?;
+
+ verify_seeds_and_pdas(
+ authority_info,
+ chunks_account_info,
+ buffer_account_info,
+ pubkey,
+ &blockhash,
+ chunks_bump,
+ buffer_bump,
+ )?;
+
+ msg!("Updating Buffer and Chunks accounts [ _, chunks_acc_len, buffer_acc_len, offset, size ]");
+
+ {
+ let buffer_data = buffer_account_info.data.borrow();
+ let chunks_data = chunks_account_info.data.borrow();
+
+ // Interpolating lens and offset increases CUs by ~1200.
+ // So we use this less pretty way since it still gives us the info we need
+ sol_log_64(
+ 0,
+ chunks_data.len() as u64,
+ buffer_data.len() as u64,
+ offset as u64,
+ data_chunk.len() as u64,
+ );
+
+ if offset as usize + data_chunk.len() > buffer_data.len() {
+ let err = CommittorError::OffsetChunkOutOfRange(
+ data_chunk.len(),
+ offset,
+ buffer_data.len(),
+ );
+ msg!("ERR: {}", err);
+ return Err(err.into());
+ }
greptile
logic: potential integer overflow vulnerability when calculating offset + data_chunk.len(). Should use checked_add
suggested fix
+ let end_offset = offset.checked_add(data_chunk.len() as u32)
+ .and_then(|sum| Some(sum as usize))
+ .ok_or(CommittorError::OffsetChunkOutOfRange(
data_chunk.len(),
offset,
buffer_data.len(),
+ ))?;
+ if end_offset > buffer_data.len() {
let err = CommittorError::OffsetChunkOutOfRange(
data_chunk.len(),
offset,
buffer_data.len(),
);
msg!("ERR: {}", err);
return Err(err.into());
}
diff block
+use solana_sdk::{
+ compute_budget::ComputeBudgetInstruction, instruction::Instruction,
+};
+
+// -----------------
+// Budgets
+// -----------------
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub struct Budget {
+ base_budget: u32,
+ per_committee: u32,
+ compute_unit_price: u64,
+}
+
+impl Default for Budget {
+ fn default() -> Self {
+ Self {
+ base_budget: 80_000,
+ per_committee: 45_000,
+ compute_unit_price: 1_000_000,
+ }
+ }
+}
+
+#[derive(Debug, Clone)]
+pub struct BufferWithReallocBudget {
+ base_budget: u32,
+ per_realloc_ix: u32,
+ compute_unit_price: u64,
+}
+
+impl BufferWithReallocBudget {
+ fn total_budget(&self, realloc_ixs_count: u32) -> u32 {
+ self.base_budget + (self.per_realloc_ix * realloc_ixs_count)
+ }
+
+ pub fn instructions(&self, realloc_ixs_count: usize) -> Vec<Instruction> {
+ let realloc_ixs_count =
+ u32::try_from(realloc_ixs_count).unwrap_or(u32::MAX);
+
+ instructions(
+ self.total_budget(realloc_ixs_count),
+ self.compute_unit_price,
+ )
+ }
+}
+
+#[derive(Debug, Clone)]
+pub struct BufferWriteChunkBudget {
+ base_budget: u32,
+ per_byte: usize,
+ compute_unit_price: u64,
+}
+
+impl BufferWriteChunkBudget {
+ fn total_budget(&self, bytes_count: usize) -> u32 {
+ self.base_budget + (self.per_byte * bytes_count) as u32
+ }
+
+ pub fn instructions(&self, bytes_count: usize) -> Vec<Instruction> {
+ instructions(self.total_budget(bytes_count), self.compute_unit_price)
+ }
+}
+
+// -----------------
+// ComputeBudgetConfig
+// -----------------
+#[derive(Debug, Clone)]
+pub struct ComputeBudgetConfig {
+ pub args_process: Budget,
+ pub finalize: Budget,
+ pub buffer_close: Budget,
+ /// The budget used for processing and process + closing a buffer.
+ /// Since we mix pure process and process + close instructions, we need to
+ /// assume the worst case and use the process + close budget for all.
+ pub buffer_process_and_close: Budget,
+ pub undelegate: Budget,
+ pub buffer_init: BufferWithReallocBudget,
+ pub buffer_realloc: BufferWithReallocBudget,
+ pub buffer_write: BufferWriteChunkBudget,
+}
+
+impl ComputeBudgetConfig {
+ pub fn new(compute_unit_price: u64) -> Self {
+ Self {
+ args_process: Budget {
+ compute_unit_price,
+ base_budget: 80_000,
+ per_committee: 35_000,
+ },
+ buffer_close: Budget {
+ compute_unit_price,
+ base_budget: 10_000,
+ per_committee: 25_000,
+ },
+ buffer_process_and_close: Budget {
+ compute_unit_price,
+ base_budget: 40_000,
+ per_committee: 45_000,
+ },
+ finalize: Budget {
+ compute_unit_price,
+ base_budget: 80_000,
+ per_committee: 25_000,
+ },
+ undelegate: Budget {
+ compute_unit_price,
+ base_budget: 40_000,
+ per_committee: 35_000,
+ },
+ buffer_init: BufferWithReallocBudget {
+ base_budget: 12_000,
+ per_realloc_ix: 6_000,
+ compute_unit_price: 1_000_000,
+ },
+ buffer_realloc: BufferWithReallocBudget {
+ base_budget: 12_000,
+ per_realloc_ix: 6_000,
+ compute_unit_price: 1_000_000,
+ },
+ buffer_write: BufferWriteChunkBudget {
+ base_budget: 10_000,
+ per_byte: 3,
+ compute_unit_price: 1_000_000,
+ },
+ }
+ }
+}
+
+impl ComputeBudgetConfig {
+ pub fn args_process_budget(&self) -> ComputeBudget {
+ ComputeBudget::Process(self.args_process)
+ }
+ pub fn buffer_close_budget(&self) -> ComputeBudget {
+ ComputeBudget::Close(self.buffer_close)
+ }
+ pub fn buffer_process_and_close_budget(&self) -> ComputeBudget {
+ ComputeBudget::ProcessAndClose(self.buffer_process_and_close)
+ }
+ pub fn finalize_budget(&self) -> ComputeBudget {
+ ComputeBudget::Finalize(self.finalize)
+ }
+ pub fn undelegate_budget(&self) -> ComputeBudget {
+ ComputeBudget::Undelegate(self.undelegate)
+ }
+}
+
+// -----------------
+// ComputeBudget
+// -----------------
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub enum ComputeBudget {
+ Process(Budget),
+ Close(Budget),
+ ProcessAndClose(Budget),
+ Finalize(Budget),
+ Undelegate(Budget),
+}
+
+impl ComputeBudget {
+ fn base_budget(&self) -> u32 {
+ use ComputeBudget::*;
+ match self {
+ Process(budget) => budget.base_budget,
+ Close(budget) => budget.base_budget,
+ ProcessAndClose(budget) => budget.base_budget,
+ Finalize(budget) => budget.base_budget,
+ Undelegate(budget) => budget.base_budget,
+ }
+ }
+
+ fn per_committee(&self) -> u32 {
+ use ComputeBudget::*;
+ match self {
+ Process(budget) => budget.per_committee,
+ Close(budget) => budget.per_committee,
+ ProcessAndClose(budget) => budget.per_committee,
+ Finalize(budget) => budget.per_committee,
+ Undelegate(budget) => budget.per_committee,
+ }
+ }
+
+ fn compute_unit_price(&self) -> u64 {
+ use ComputeBudget::*;
+ match self {
+ Process(budget) => budget.compute_unit_price,
+ Close(budget) => budget.compute_unit_price,
+ ProcessAndClose(budget) => budget.compute_unit_price,
+ Finalize(budget) => budget.compute_unit_price,
+ Undelegate(budget) => budget.compute_unit_price,
+ }
+ }
+
+ fn total_budget(&self, committee_count: u32) -> u32 {
+ self.base_budget() + (self.per_committee() * committee_count)
+ }
greptile
logic: Potential integer overflow when calculating total_budget with large committee_count values. Should use checked_mul and checked_add.
suggested fix
fn total_budget(&self, committee_count: u32) -> u32 {
+ self.per_committee()
+ .checked_mul(committee_count)
+ .and_then(|product| product.checked_add(self.base_budget()))
+ .unwrap_or(u32::MAX)
}
diff block
+use solana_sdk::{
+ compute_budget::ComputeBudgetInstruction, instruction::Instruction,
+};
+
+// -----------------
+// Budgets
+// -----------------
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub struct Budget {
+ base_budget: u32,
+ per_committee: u32,
+ compute_unit_price: u64,
+}
+
+impl Default for Budget {
+ fn default() -> Self {
+ Self {
+ base_budget: 80_000,
+ per_committee: 45_000,
+ compute_unit_price: 1_000_000,
+ }
+ }
+}
+
+#[derive(Debug, Clone)]
+pub struct BufferWithReallocBudget {
+ base_budget: u32,
+ per_realloc_ix: u32,
+ compute_unit_price: u64,
+}
+
+impl BufferWithReallocBudget {
+ fn total_budget(&self, realloc_ixs_count: u32) -> u32 {
+ self.base_budget + (self.per_realloc_ix * realloc_ixs_count)
+ }
+
+ pub fn instructions(&self, realloc_ixs_count: usize) -> Vec<Instruction> {
+ let realloc_ixs_count =
+ u32::try_from(realloc_ixs_count).unwrap_or(u32::MAX);
+
+ instructions(
+ self.total_budget(realloc_ixs_count),
+ self.compute_unit_price,
+ )
+ }
+}
+
+#[derive(Debug, Clone)]
+pub struct BufferWriteChunkBudget {
+ base_budget: u32,
+ per_byte: usize,
+ compute_unit_price: u64,
+}
+
+impl BufferWriteChunkBudget {
+ fn total_budget(&self, bytes_count: usize) -> u32 {
+ self.base_budget + (self.per_byte * bytes_count) as u32
+ }
greptile
logic: Potential integer overflow when multiplying per_byte with bytes_count before casting to u32. Need bounds checking or safe arithmetic.
suggested fix
+ self.base_budget + u32::try_from(self.per_byte.checked_mul(bytes_count).unwrap_or(u32::MAX as usize)).unwrap_or(u32::MAX)
}
diff block
+package io.constellationnetwork.schema
+
+import cats.implicits.{catsSyntaxEitherId, catsSyntaxSemigroup, toBifunctorOps}
+
+import scala.util.control.NoStackTrace
+
+import derevo.cats.{eqv, show}
+import derevo.circe.magnolia.{decoder, encoder}
+import derevo.derive
+import eu.timepit.refined.types.all.NonNegLong
+
+object NonNegLongOps {
+ @derive(eqv, encoder, decoder, show)
+ sealed trait NonNegLongArithmeticError extends NoStackTrace
+ case object AmountOverflow extends NonNegLongArithmeticError
+ case object AmountUnderflow extends NonNegLongArithmeticError
+
+ implicit class NonNegLongOper(value: NonNegLong) {
+ def plus(that: NonNegLong): Either[AmountOverflow.type, NonNegLong] = {
+ val sum = value.value |+| that.value
+
+ if (sum >= value.value && sum >= that.value) {
+ NonNegLong.unsafeFrom(sum).asRight
+ } else AmountOverflow.asLeft[NonNegLong]
greptile
logic: Potential integer overflow vulnerability. The comparison `sum >= value.value` can give incorrect results when sum overflows. Consider using Long.MAX_VALUE boundary check.
suggested fix
+ if (value.value <= Long.MAX_VALUE - that.value) {
NonNegLong.unsafeFrom(sum).asRight
} else AmountOverflow.asLeft[NonNegLong]
diff block
let file_size_map = files
.iter()
- .filter_map(|(_, f, s)| {
+ .filter_map(|(_, f, s, _)| {
if *s > cfg.cache_latest_files.download_node_size * 1024 * 1024 {
None
greptile
logic: Potential integer overflow when comparing i64 with usize multiplication result
diff block
+use std::collections::{HashMap, HashSet};
+
+use borsh::{BorshDeserialize, BorshSerialize};
+use solana_account::{Account, AccountSharedData, ReadableAccount};
+use solana_program::clock::Slot;
+use solana_pubkey::Pubkey;
+
+use super::{
+ changeset_chunks::{ChangesetChunks, ChangesetChunksIter},
+ chunks::Chunks,
+};
+
+// -----------------
+// ChangedAccount
+// -----------------
+pub type ChangedBundle = Vec<(Pubkey, ChangedAccount)>;
+
+#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Clone, Debug)]
+pub enum ChangedAccount {
+ Full {
+ lamports: u64,
+ data: Vec<u8>,
+ /// The original owner of the delegated account on chain
+ owner: Pubkey,
+ /// This id will be the same for accounts that need to be committed together atomically
+ /// For single commit accounts it is still set for consistency
+ bundle_id: u64,
+ },
+ // NOTE: placeholder for later without breaking existing
+ // buffers
+ Diff,
+}
+
+#[derive(Debug, Default, Clone, PartialEq, Eq)]
+pub struct ChangedAccountMeta {
+ /// The on chain and ephemeral address of the delegated account
+ pub pubkey: Pubkey,
+ /// The lamports the account holds in the ephemeral
+ pub lamports: u64,
+ /// The original owner of the delegated account on chain
+ pub owner: Pubkey,
+ /// This id will be the same for accounts that need to be committed together atomically
+ /// For single commit accounts it is still set for consistency
+ pub bundle_id: u64,
+}
+
+impl From<(&Pubkey, &ChangedAccount)> for ChangedAccountMeta {
+ fn from((pubkey, changed_account): (&Pubkey, &ChangedAccount)) -> Self {
+ match changed_account {
+ ChangedAccount::Full {
+ lamports,
+ owner,
+ bundle_id,
+ ..
+ } => Self {
+ pubkey: *pubkey,
+ lamports: *lamports,
+ owner: *owner,
+ bundle_id: *bundle_id,
+ },
+ ChangedAccount::Diff => {
+ unreachable!("We don't yet support account diffs")
+ }
+ }
+ }
+}
+
+impl From<(Account, u64)> for ChangedAccount {
+ fn from((account, bundle_id): (Account, u64)) -> Self {
+ Self::Full {
+ lamports: account.lamports,
+ // NOTE: the owner of the account in the ephemeral is set to the original account owner
+ owner: account.owner,
+ data: account.data,
+ bundle_id,
+ }
+ }
+}
+
+impl From<(AccountSharedData, u64)> for ChangedAccount {
+ fn from((value, bundle_id): (AccountSharedData, u64)) -> Self {
+ Self::Full {
+ lamports: value.lamports(),
+ owner: *value.owner(),
+ data: value.data().to_vec(),
+ bundle_id,
+ }
+ }
+}
+
+impl ChangedAccount {
+ pub(crate) fn into_inner(self) -> (u64, Pubkey, Vec<u8>, u64) {
+ use ChangedAccount::*;
+ match self {
+ Full {
+ lamports,
+ owner,
+ data,
+ bundle_id,
+ } => (lamports, owner, data, bundle_id),
+ Diff => unreachable!("We don't yet support account diffs"),
+ }
+ }
+
+ pub fn lamports(&self) -> u64 {
+ match self {
+ Self::Full { lamports, .. } => *lamports,
+ Self::Diff => unreachable!("We don't yet support account diffs"),
+ }
+ }
+
+ pub fn data(&self) -> &[u8] {
+ match self {
+ Self::Full { data, .. } => data,
+ Self::Diff => unreachable!("We don't yet support account diffs"),
+ }
+ }
+
+ pub fn owner(&self) -> Pubkey {
+ match self {
+ Self::Full { owner, .. } => *owner,
+ Self::Diff => unreachable!("We don't yet support account diffs"),
+ }
+ }
+
+ pub fn bundle_id(&self) -> u64 {
+ match self {
+ Self::Full { bundle_id, .. } => *bundle_id,
+ Self::Diff => unreachable!("We don't yet support account diffs"),
+ }
+ }
+}
+
+// -----------------
+// ChangeSet
+// -----------------
+
+/// This is data structure which holds the account changes to commit to chain.
+/// Locally it will be filled with the changes to commit.
+/// On chain it is initialized as empty at first and then is filled from the
+/// local changeset via multiple transactions.
+/// A related [Chunks] account is used in order to track which changes have been
+/// applied successfully.
+#[derive(Debug, Default, Clone, PartialEq, Eq)]
+pub struct Changeset {
+ /// The accounts that should be updated
+ pub accounts: HashMap<Pubkey, ChangedAccount>,
+ /// The ephemeral slot at which those changes were requested
+ pub slot: Slot,
+ /// The accounts that should be undelegated after they were committed
+ pub accounts_to_undelegate: HashSet<Pubkey>,
+}
+
+/// The meta data of the changeset which can be used to capture information about
+/// the changeset before transferring ownership. Createing this metadata is
+/// a lot cheaper than copying the entire changeset which includes the accounts data.
+/// Thus it can be used to capture information to include with error responses.
+#[derive(Debug, Default, Clone, PartialEq, Eq)]
+pub struct ChangesetMeta {
+ /// The accounts that should be updated
+ pub accounts: Vec<ChangedAccountMeta>,
+ /// The ephemeral slot at which those changes were requested
+ pub slot: Slot,
+ /// The accounts that should be undelegated after they were committed
+ pub accounts_to_undelegate: HashSet<Pubkey>,
+}
+
+impl ChangesetMeta {
+ /// Separates information per account including the following:
+ /// - account commit metadata
+ /// - slot at which commit was requested
+ /// - if the account should be undelegated after it was committed
+ pub fn into_account_infos(self) -> Vec<(ChangedAccountMeta, Slot, bool)> {
+ self.accounts
+ .into_iter()
+ .map(|account| {
+ let undelegate =
+ self.accounts_to_undelegate.contains(&account.pubkey);
+ (account, self.slot, undelegate)
+ })
+ .collect()
+ }
+}
+
+impl From<&Changeset> for ChangesetMeta {
+ fn from(changeset: &Changeset) -> Self {
+ let accounts = changeset
+ .accounts
+ .iter()
+ .map(ChangedAccountMeta::from)
+ .collect();
+ Self {
+ accounts,
+ slot: changeset.slot,
+ accounts_to_undelegate: changeset.accounts_to_undelegate.clone(),
+ }
+ }
+}
+
+#[derive(Debug, Default, Clone, PartialEq, Eq)]
+pub struct ChangesetBundles {
+ /// The bundles, each of which needs to be committed atomically
+ pub bundles: Vec<ChangedBundle>,
+ /// The ephemeral slot at which those changes were requested
+ pub slot: Slot,
+ /// The accounts that should be undelegated after they were committed
+ pub accounts_to_undelegate: HashSet<Pubkey>,
+}
+
+impl Changeset {
+ /// Adds an account to the change set.
+ /// If it already exists, it will be replaced, thus the caller needs
+ /// to ensure that conflicting changes are added in the right order, i.e.
+ /// the last update needs to be added last.
+ ///
+ /// - **pubkey** public key of the account
+ /// - **account** account to add
+ ///
+ /// *returns* true if the account was already present and was replaced
+ pub fn add<T: Into<ChangedAccount>>(
+ &mut self,
+ pubkey: Pubkey,
+ account: T,
+ ) -> bool {
+ self.accounts.insert(pubkey, account.into()).is_some()
+ }
+
+ /// This method should be called for all accounts that we want to
+ /// undelegate after committing them.
+ pub fn request_undelegation(&mut self, pubkey: Pubkey) {
+ self.accounts_to_undelegate.insert(pubkey);
+ }
+
+ /// When we're ready to commit this changeset we convert it into
+ /// a [CommitableChangeSet] which allows to commit the changes in chunks.
+ pub fn into_committables(self, chunk_size: u16) -> Vec<CommitableAccount> {
+ self.accounts
+ .into_iter()
+ .map(|(pubkey, acc)| {
+ let (lamports, owner, data, bundle_id) = acc.into_inner();
+ CommitableAccount::new(
+ pubkey,
+ owner,
+ data,
+ lamports,
+ chunk_size,
+ self.slot,
+ self.accounts_to_undelegate.contains(&pubkey),
+ bundle_id,
+ )
+ })
+ .collect::<Vec<_>>()
+ }
+
+ pub fn account_keys(&self) -> Vec<&Pubkey> {
+ self.accounts.keys().collect()
+ }
+
+ pub fn undelegate_keys(&self) -> Vec<&Pubkey> {
+ self.accounts_to_undelegate.iter().collect()
+ }
+
+ pub fn owners(&self) -> HashMap<Pubkey, Pubkey> {
+ self.accounts
+ .iter()
+ .map(|(pubkey, account)| (*pubkey, account.owner()))
+ .collect()
+ }
+
+ /// Splits the accounts into bundles that need to be committed together
+ /// keeping each bundle as small as possible.
+ /// Accounts without a bundle id each get their own bundle here.
+ /// The return value returns info about accounts needing to be delegated and
+ /// the slot at which the changeset was created.
+ pub fn into_small_changeset_bundles(self) -> ChangesetBundles {
+ let mut bundles: HashMap<u64, ChangedBundle> = HashMap::new();
+ let accounts_to_undelegate = self.accounts_to_undelegate;
+ let slot = self.slot;
+ for (pubkey, account) in self.accounts.into_iter() {
+ bundles
+ .entry(account.bundle_id())
+ .or_default()
+ .push((pubkey, account));
+ }
+ let bundles = bundles.into_values().collect::<Vec<_>>();
+
+ ChangesetBundles {
+ bundles,
+ slot,
+ accounts_to_undelegate,
+ }
+ }
+
+ pub fn is_empty(&self) -> bool {
+ self.accounts.is_empty()
+ }
+
+ pub fn len(&self) -> usize {
+ self.accounts.len()
+ }
+
+ pub fn overlaps(change_sets: &[&Self]) -> Vec<Pubkey> {
+ let mut overlapping = HashSet::new();
+ for change_set in change_sets {
+ for (pubkey, _) in change_set.accounts.iter() {
+ if overlapping.contains(pubkey) {
+ continue;
+ }
+ for other_change_set in change_sets {
+ if other_change_set == change_set {
+ continue;
+ }
+ if other_change_set.accounts.contains_key(pubkey) {
+ overlapping.insert(*pubkey);
+ }
+ }
+ }
+ }
+ overlapping.into_iter().collect()
+ }
+
+ pub fn contains(&self, pubkey: &Pubkey) -> bool {
+ self.accounts.contains_key(pubkey)
+ }
+}
+
+// -----------------
+// CommitableChangeSet
+// -----------------
+/// There is one committable per account that we are trying to commit
+#[derive(Debug)]
+pub struct CommitableAccount {
+ /// The on chain address of the account
+ pub pubkey: Pubkey,
+ /// The original owner of the delegated account on chain
+ pub delegated_account_owner: Pubkey,
+ /// The account data to commit
+ pub data: Vec<u8>,
+ /// The lamports that the account holds in the ephemeral
+ pub lamports: u64,
+ /// Keep track of which part of the account data has been committed
+ chunks: Chunks,
+ /// The size of each data chunk that we send to fill the buffer
+ chunk_size: u16,
+ /// The ephemeral slot at which those changes were requested
+ pub slot: Slot,
+ /// If we also undelegate the account after committing it
+ pub undelegate: bool,
+ /// This id will be the same for accounts that need to be committed together atomically
+ /// For single commit accounts it is still set for consistency
+ pub bundle_id: u64,
+}
+
+impl CommitableAccount {
+ #[allow(clippy::too_many_arguments)] // internal API
+ pub(crate) fn new(
+ pubkey: Pubkey,
+ delegated_account_owner: Pubkey,
+ data: Vec<u8>,
+ lamports: u64,
+ chunk_size: u16,
+ slot: Slot,
+ undelegate: bool,
+ bundle_id: u64,
+ ) -> Self {
+ let len = data.len();
+ let chunk_count = if chunk_size == 0 {
+ // Special case for when the commit info is handled without chunking
+ 1
+ } else {
+ let count = len / chunk_size as usize;
+ if len % chunk_size as usize > 0 {
+ count + 1
+ } else {
+ count
+ }
+ };
greptile
logic: Potential integer overflow when calculating chunk_count if data.len() is large. Should add bounds checking.
diff block
value.to_lowercase()
}
+fn handle_snowflake_timestamp(value: &Value) -> Option<Value> {
+ if let Value::Object(map) = value {
+ if map.contains_key("epoch") {
+ // If epoch is null, return null
+ if map["epoch"].is_null() {
+ return Some(Value::Null);
+ }
+
+ // If we have a valid epoch, convert it
+ if let Some(epoch) = map["epoch"].as_i64() {
+ match parse_snowflake_timestamp(epoch, 0) {
greptile
logic: potential integer overflow when converting epoch to timestamp. Should check if epoch is within valid range before calling parse_snowflake_timestamp
suggested fix
if let Some(epoch) = map["epoch"].as_i64() {
+ if epoch > i64::MIN && epoch < i64::MAX - 999_999_999 {
match parse_snowflake_timestamp(epoch, 0) {
diff block
+use crate::{
+ error::Error,
+ symbol_data::{SymbolData, SymbolDataType},
+ utils::assert_at_least_as_long_as,
+};
+
+// NOTE: see posthog/api/error_tracking.py
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct SourceAndMap {
+ pub minified_source: String,
+ pub sourcemap: String,
+}
+
+impl SymbolData for SourceAndMap {
+ fn from_bytes(data: Vec<u8>) -> Result<Self, Error> {
+ assert_at_least_as_long_as(8, data.len())?;
+ let s_len = u64::from_le_bytes(data[0..8].try_into().unwrap()) as usize;
greptile
logic: potential integer overflow when converting u64 to usize on 32-bit systems
diff block
}
let jitter = rand::random::<u64>() % 50;
- warn!("Issue failed: {:?}, sleeping for {}ms", e, jitter);
- tokio::time::sleep(Duration::from_millis(jitter)).await;
+ let delay: u64 = tries * UPDATE_RETRY_DELAY_MS + jitter;
greptile
logic: Potential integer overflow if tries * UPDATE_RETRY_DELAY_MS exceeds u64::MAX. Add overflow check or use checked_mul
diff block
let cpu = unwrap_ref!(task.cpu);
let memory = unwrap_ref!(task.memory);
- usage.cpu += unwrap!(cpu.cpu_shares) as u32;
+ // MHz to Millicores
+ usage.cpu += (unwrap!(cpu.cpu_shares)
+ * total_cpu_millicores as i64 / total_cpu_shares)
+ as u32;
greptile
logic: Potential integer overflow when multiplying cpu_shares with total_cpu_millicores. Consider using u64 for intermediate calculation.
suggested fix
+ usage.cpu += (unwrap!(cpu.cpu_shares) as u64
+ * total_cpu_millicores as u64 / total_cpu_shares as u64)
as u32;
diff block
}
}
-fn prepare_if_nonce_account(
- address: &Pubkey,
- account: &mut AccountSharedData,
- execution_result: &Result<()>,
- is_fee_payer: bool,
- maybe_nonce: Option<(&NonceFull, bool)>,
- &durable_nonce: &DurableNonce,
- lamports_per_signature: u64,
-) -> bool {
- if let Some((nonce, rollback)) = maybe_nonce {
- if address == nonce.address() {
- if rollback {
- // The transaction failed which would normally drop the account
- // processing changes, since this account is now being included
- // in the accounts written back to the db, roll it back to
- // pre-processing state.
- *account = nonce.account().clone();
- }
+fn collect_accounts_for_successful_tx<'a, T: SVMMessage>(
+ collected_accounts: &mut Vec<(Pubkey, AccountSharedData)>,
+ transaction: &'a T,
+ transaction_accounts: &'a [TransactionAccount],
+) {
+ for (i, (address, account)) in
+ (0..transaction.account_keys().len()).zip(transaction_accounts)
+ {
+ if !transaction.is_writable(i) {
+ continue;
+ }
- // Advance the stored blockhash to prevent fee theft by someone
- // replaying nonce transactions that have failed with an
- // `InstructionError`.
- //
- // Since we know we are dealing with a valid nonce account,
- // unwrap is safe here
- let nonce_versions =
- StateMut::<NonceVersions>::state(nonce.account()).unwrap();
- if let NonceState::Initialized(ref data) = nonce_versions.state() {
- let nonce_state = NonceState::new_initialized(
- &data.authority,
- durable_nonce,
- lamports_per_signature,
- );
- let nonce_versions = NonceVersions::new(nonce_state);
- account.set_state(&nonce_versions).unwrap();
- }
- true
- } else {
- if execution_result.is_err() && is_fee_payer {
- if let Some(fee_payer_account) = nonce.fee_payer_account() {
- // Instruction error and fee-payer for this nonce tx is not
- // the nonce account itself, rollback the fee payer to the
- // fee-paid original state.
- *account = fee_payer_account.clone();
- }
- }
+ // Accounts that are invoked and also not passed as an instruction
+ // account to a program don't need to be stored because it's assumed
+ // to be impossible for a committable transaction to modify an
+ // invoked account if said account isn't passed to some program.
+ if transaction.is_invoked(i) && !transaction.is_instruction_account(i) {
+ continue;
+ }
+
+ collected_accounts.push((*address, account.clone()));
+ }
+}
- false
+fn collect_accounts_for_failed_tx<'a, T: SVMMessage>(
+ collected_accounts: &mut Vec<(Pubkey, AccountSharedData)>,
+ transaction: &'a T,
+ rollback_accounts: &'a RollbackAccounts,
+) {
+ let fee_payer_address = transaction.fee_payer();
+ match rollback_accounts {
+ RollbackAccounts::FeePayerOnly { fee_payer_account } => {
+ collected_accounts
+ .push((*fee_payer_address, fee_payer_account.clone()));
+ }
+ RollbackAccounts::SameNonceAndFeePayer { nonce } => {
+ collected_accounts
+ .push((*nonce.address(), nonce.account().clone()));
+ }
+ RollbackAccounts::SeparateNonceAndFeePayer {
+ nonce,
+ fee_payer_account,
+ } => {
+ collected_accounts
+ .push((*fee_payer_address, fee_payer_account.clone()));
+
+ collected_accounts
+ .push((*nonce.address(), nonce.account().clone()));
}
- } else {
- false
}
}
+fn max_number_of_accounts_to_collect(
+ txs: &[impl SVMMessage],
+ processing_results: &[TransactionProcessingResult],
+) -> usize {
+ processing_results
+ .iter()
+ .zip(txs)
+ .filter_map(|(processing_result, tx)| {
+ processing_result
+ .processed_transaction()
+ .map(|processed_tx| (processed_tx, tx))
+ })
+ .map(|(processed_tx, tx)| match processed_tx {
+ ProcessedTransaction::Executed(executed_tx) => {
+ match executed_tx.execution_details.status {
+ Ok(_) => tx.num_write_locks() as usize,
+ Err(_) => {
+ executed_tx.loaded_transaction.rollback_accounts.count()
+ }
+ }
greptile
logic: Potential integer overflow if num_write_locks() returns max value
diff block
+#include "dlgTriggerEditorCommand.h"
+#include "Host.h"
+#include "LuaInterface.h"
+#include "VarUnit.h"
+#include "XMLimport.h"
+#include "dlgActionMainArea.h"
+#include "dlgAliasMainArea.h"
+#include "dlgKeysMainArea.h"
+#include "dlgScriptsMainArea.h"
+#include "dlgTriggerEditor.h"
+#include "dlgTriggerPatternEdit.h"
+#include "mudlet.h"
+#include <QPointer>
+
+AddTriggerCommand::AddTriggerCommand(Host* pHost, TTreeWidget* treeWidgetTriggers, bool isFolder, QUndoCommand* parent) : QUndoCommand(parent), mpHost(pHost)
+{
+ mpTreeWidgetTriggers = treeWidgetTriggers;
+ mIsFolder = isFolder;
+}
+
+void AddTriggerCommand::undo()
+{
+ if (!mpItem) {
+ return;
+ }
+
+ mpEditor->selectTriggerByID(mItemID);
+ mpItem = mpTreeWidgetTriggers->currentItem();
+ mpParent = mpItem->parent();
+
+ if (mpParent) {
+ mpParent->removeChild(mpItem);
+ const int parentId = mpParent->data(0, Qt::UserRole).toInt();
+ TTrigger* pParent = mpHost->getTriggerUnit()->getTrigger(parentId);
+ if (pParent) {
+ TTrigger* pT = mpHost->getTriggerUnit()->getTrigger(mItemID);
+ pParent->popChild(pT);
+ }
+ } else {
+ qDebug() << "parent is null ";
+ }
+}
+
+void AddTriggerCommand::redo()
+{
+ if (!mpEditor) {
+ return;
+ }
+ if (!mpItem) {
+ mpEditor->addTrigger(mIsFolder);
+ mpItem = mpTreeWidgetTriggers->currentItem();
+ mCurrentIndex = mpTreeWidgetTriggers->currentIndex();
+ mSiblingRow = mCurrentIndex.row();
+ mpParent = mpItem->parent();
+ mItemID = mpItem->data(0, Qt::UserRole).toInt();
+
+ } else {
+ const int parentId = mpParent->data(0, Qt::UserRole).toInt();
+ TTrigger* pParent = mpHost->getTriggerUnit()->getTrigger(parentId);
+ if (mIsFolder) {
+ mpParent->addChild(mpItem);
+ } else {
+ mpParent->insertChild(mSiblingRow, mpItem);
+ }
+ if (pParent) {
+ TTrigger* pT = mpHost->getTriggerUnit()->getTrigger(mItemID);
+ pParent->addChild(pT);
+ }
+
+ mpEditor->selectTriggerByID(mItemID);
+ }
+
+ setText(QObject::tr("Add Trigger"));
+}
+
+PasteTriggerCommand::PasteTriggerCommand(Host* pHost, TTreeWidget* treeWidgetTriggers, QUndoCommand* parent) : QUndoCommand(parent), mpHost(pHost)
+{
+ mpTreeWidgetTriggers = treeWidgetTriggers;
+ mpItemTrigger = nullptr;
+}
+
+void PasteTriggerCommand::undo()
+{
+ if (!mpEditor) {
+ return;
+ }
+
+ mpEditor->selectTriggerByID(mImportedItemID);
+ mpItem = mpTreeWidgetTriggers->currentItem();
+ mpParent = mpItem->parent();
+ if (mpParent) {
+ mpParent->removeChild(mpItem);
+ const int parentId = mpParent->data(0, Qt::UserRole).toInt();
+ TTrigger* pParent = mpHost->getTriggerUnit()->getTrigger(parentId);
+ if (pParent) {
+ TTrigger* pT = mpHost->getTriggerUnit()->getTrigger(mImportedItemID);
+ pParent->popChild(pT);
+ }
+ } else {
+ qDebug() << "parent is null ";
+ }
+}
+
+void PasteTriggerCommand::redo()
+{
+ if (!mpItem) {
+ auto parent = mpTreeWidgetTriggers->currentIndex().parent();
+ auto parentRow = parent.row();
+
+ mSiblingRow = mpTreeWidgetTriggers->currentIndex().row() + 1;
+ mpItemTrigger = mpHost->getTriggerUnit()->getTrigger(mImportedItemID);
+ const int parentId = mpParent->data(0, Qt::UserRole).toInt();
+ mpHost->getTriggerUnit()->reParentTrigger(mImportedItemID, 0, parentId, parentRow, mSiblingRow);
+
+ mpEditor->selectTriggerByID(mImportedItemID);
+ mpItem = mpTreeWidgetTriggers->currentItem();
+ mpParent = mpItem->parent();
+ } else {
+ const int parentId = mpParent->data(0, Qt::UserRole).toInt();
+ TTrigger* pParent = mpHost->getTriggerUnit()->getTrigger(parentId);
+ const int id = mpItem->data(0, Qt::UserRole).toInt();
+ TTrigger* pT = mpHost->getTriggerUnit()->getTrigger(id);
+ mpItemTrigger = pT;
+
+ if (pParent) {
+ mpParent->insertChild(mSiblingRow, mpItem);
+ } else {
+ mpParent->addChild(mpItem);
+ }
+ if (pParent) {
+ pParent->addChild(mpItemTrigger);
+ }
+
+ mpEditor->selectTriggerByID(id);
+ }
+
+ setText(QObject::tr("Paste trigger"));
+}
+
+DeleteTriggerCommand::DeleteTriggerCommand(Host* pHost, QTreeWidgetItem* pItem, TTreeWidget* treeWidgetTriggers, QUndoCommand* parent) : QUndoCommand(parent), mpHost(pHost)
+{
+ mpItem = pItem;
+ mpTreeWidgetTriggers = treeWidgetTriggers;
+}
+
+void DeleteTriggerCommand::undo()
+{
+ if (!mpItem) {
+ return;
+ }
+ mpEditor->selectTriggerByID(mParentID);
+ auto mParent = mpTreeWidgetTriggers->currentItem();
+ mpParent = mParent;
+ if (mpParent) {
+ QList<QTreeWidgetItem*> childList;
+ mpTreeWidgetTriggers->getAllChildren(mpItem, childList);
+ if (mpItemTriggerList.size() != childList.size()) {
+ return;
+ }
+ for (int i = 0; i < childList.size(); ++i) {
+ mpItemTriggerList[i]->setTemporary(false);
+ mpItemTriggerList[i]->registerTrigger();
+ childList[i]->setData(0, Qt::UserRole, mpItemTriggerList[i]->getID());
+ }
+ mpParent->insertChild(mSiblingRow, mpItem);
+
+ const int itemId = mpItem->data(0, Qt::UserRole).toInt();
+
+ const int parentId = mpParent->data(0, Qt::UserRole).toInt();
+ mpHost->getTriggerUnit()->reParentTrigger(itemId, -1, parentId, mParentRow, mSiblingRow);
+ mpEditor->selectTriggerByID(itemId);
+ mpItem = mpTreeWidgetTriggers->currentItem();
+ } else {
+ qDebug() << "parent is null ";
+ }
+}
+void DeleteTriggerCommand::redo()
+{
+ if (!mpEditor || !mpHost) {
+ return;
+ }
+ if (mpItem) {
+ auto parent = mCurrentIndex.parent();
+ auto parentRow = parent.row();
+ auto parentId = parent.data(Qt::UserRole).toInt();
+ mParentID = parentId;
+ const int siblingRow = mCurrentIndex.row();
+ mParentRow = parentRow;
+ mSiblingRow = siblingRow;
+ int itemId = mpItem->data(0, Qt::UserRole).toInt();
+ mpParent = mpItem->parent();
+ mpEditor->selectTriggerByID(itemId);
+ mpItemTriggerList.clear();
+ recurseTriggers(mpItem, mpHost);
+ mpEditor->delete_trigger();
+ }
+ setText(QObject::tr("Delete trigger"));
+}
+
+void DeleteTriggerCommand::recurseTriggers(QTreeWidgetItem* mpItem, QPointer<Host> mpHost)
+{
+ TTrigger* ptr = nullptr;
+ QList<QTreeWidgetItem*> childList;
+ mpTreeWidgetTriggers->getAllChildren(mpItem, childList);
+ for (int i = 0; i < childList.size(); ++i) {
+ const int pID = childList[i]->data(0, Qt::UserRole).toInt();
+ TTrigger* pChild = mpHost->getTriggerUnit()->getTrigger(pID);
+ if (!pChild) {
+ continue;
+ }
+ if (pChild->getParent() && mpItemTriggerList.size() > 0) {
+ for (int j = 0; j < mpItemTriggerList.size(); j++) {
+ if (mpItemTriggerList[j]->mID == pChild->getParent()->mID) {
+ ptr = new TTrigger(mpItemTriggerList[j], mpHost);
+ break;
+ }
+ }
+ } else {
+ ptr = new TTrigger(pChild->mName, pChild->mPatterns, pChild->getRegexCodePropertyList(), pChild->isMultiline(), mpHost);
+ }
+ ptr->setID(pChild->mID);
+ ptr->setName(pChild->mName);
+ ptr->setCommand(pChild->getCommand());
+ ptr->setRegexCodeList(pChild->getPatternsList(), pChild->getRegexCodePropertyList());
+ ptr->setIsFolder(pChild->isFolder());
+ ptr->setIsActive(pChild->isActive());
+ ptr->setIsMultiline(pChild->isMultiline());
+ ptr->mPerlSlashGOption = pChild->mPerlSlashGOption;
+ ptr->mFilterTrigger = pChild->mFilterTrigger;
+ ptr->mStayOpen = pChild->mStayOpen;
+ ptr->setConditionLineDelta(pChild->getConditionLineDelta());
+ ptr->mSoundFile = pChild->mSoundFile;
+ ptr->setIsColorizerTrigger(pChild->isColorizerTrigger());
+ ptr->setColorizerFgColor(pChild->getFgColor());
+ ptr->setColorizerBgColor(pChild->getBgColor());
+ ptr->setTemporary(true);
+ mpItemTriggerList.append(ptr);
+ }
+}
+
+MoveTriggerCommand::MoveTriggerCommand(Host* pHost,
+ TTreeWidget* treeWidgetTriggers,
+ int childID,
+ int oldParentID,
+ int newParentID,
+ int parentPosition,
+ int childPosition,
+ int prevParentPosition,
+ int prevChildPosition,
+ QUndoCommand* parent)
+: QUndoCommand(parent), mpHost(pHost)
+{
+ mChildID = childID;
+ mOldParentID = oldParentID;
+ mNewParentID = newParentID;
+ mParentPosition = parentPosition;
+ mChildPosition = childPosition;
+ mPrevParentPosition = prevParentPosition;
+ mPrevChildPosition = prevChildPosition;
+ mpTreeWidgetTriggers = treeWidgetTriggers;
+}
+
+void MoveTriggerCommand::undo()
+{
+ if (!mpHost) {
+ return;
+ }
+
+ mpEditor->selectTriggerByID(mItemID);
+ mpItem = mpTreeWidgetTriggers->currentItem();
+
+ mpEditor->selectTriggerByID(mOldParentID);
+ mpPrevParentItem = mpTreeWidgetTriggers->currentItem();
+
+ mpEditor->selectTriggerByID(mNewParentID);
+ mpParentItem = mpTreeWidgetTriggers->currentItem();
+
+ mpHost->getTriggerUnit()->reParentTrigger(mChildID, mNewParentID, mOldParentID, mPrevParentPosition, mPrevChildPosition);
+ mpParentItem->removeChild(mpItem);
+
+ mpPrevParentItem->insertChild(mPrevChildPosition, mpItem);
+
+ mpTreeWidgetTriggers->setCurrentItem(mpItem);
+}
+
+void MoveTriggerCommand::redo()
+{
+ if (!mpEditor) {
+ return;
+ }
+ mItemID = mpItem->data(0, Qt::UserRole).toInt();
+ mpHost->getTriggerUnit()->reParentTrigger(mChildID, mOldParentID, mNewParentID, mParentPosition, mChildPosition);
+ if (mpPrevParentItem) {
+ mpPrevParentItem->removeChild(mpItem);
+ }
+ if (mpParentItem) {
+ const int count = mpParentItem->childCount();
+ mpParentItem->insertChild(mChildPosition, mpItem);
+ }
+
+ setText(QObject::tr("Move trigger"));
+}
+
+AddAliasCommand::AddAliasCommand(Host* pHost, TTreeWidget* treeWidget_aliases, bool isFolder, QUndoCommand* parent) : QUndoCommand(parent), mpHost(pHost)
+{
+ mpTreeWidgetAliases = treeWidget_aliases;
+ mIsFolder = isFolder;
+}
+
+void AddAliasCommand::undo()
+{
+ if (!mpItem) {
+ return;
+ }
+
+ mpEditor->selectAliasByID(mItemID);
+ mpItem = mpTreeWidgetAliases->currentItem();
+
+ mpParent = mpItem->parent();
+ if (mpParent) {
+ mpParent->removeChild(mpItem);
+ const int parentId = mpParent->data(0, Qt::UserRole).toInt();
+ TAlias* pParent = mpHost->getAliasUnit()->getAlias(parentId);
+ if (pParent) {
+ TAlias* pT = mpHost->getAliasUnit()->getAlias(mItemID);
+ pParent->popChild(pT);
+ }
+ } else {
+ qDebug() << "parent is null ";
+ }
+}
+
+void AddAliasCommand::redo()
+{
+ if (!mpEditor) {
+ return;
+ }
+ if (!mpItem) {
+ mpEditor->addAlias(mIsFolder);
+ mpItem = mpTreeWidgetAliases->currentItem();
+ mCurrentIndex = mpTreeWidgetAliases->currentIndex();
+ mSiblingRow = mCurrentIndex.row();
+ mpParent = mpItem->parent();
+ mItemID = mpItem->data(0, Qt::UserRole).toInt();
+ } else {
+ const int parentId = mpParent->data(0, Qt::UserRole).toInt();
+ TAlias* pParent = mpHost->getAliasUnit()->getAlias(parentId);
+ if (mIsFolder) {
+ mpParent->addChild(mpItem);
+ } else {
+ mpParent->insertChild(mSiblingRow, mpItem);
+ }
+ if (pParent) {
+ TAlias* pT = mpHost->getAliasUnit()->getAlias(mItemID);
+ pParent->addChild(pT);
+ }
+
+ mpEditor->selectAliasByID(mItemID);
+ }
+
+ setText(QObject::tr("Add alias"));
+}
+
+PasteAliasCommand::PasteAliasCommand(Host* pHost, TTreeWidget* treeWidgetAliases, QUndoCommand* parent) : QUndoCommand(parent), mpHost(pHost)
+{
+ mpTreeWidgetAliases = treeWidgetAliases;
+}
+
+void PasteAliasCommand::undo()
+{
+ if (!mpEditor) {
+ return;
+ }
+
+ mpEditor->selectAliasByID(mImportedItemID);
+ mpItem = mpTreeWidgetAliases->currentItem();
+ TAlias* pT = mpHost->getAliasUnit()->getAlias(mImportedItemID);
+ mpItemAlias = pT;
+ mpParent = mpItem->parent();
+ if (mpParent) {
+ mpParent->removeChild(mpItem);
+ const int parentId = mpParent->data(0, Qt::UserRole).toInt();
+ TAlias* pParent = mpHost->getAliasUnit()->getAlias(parentId);
+ if (pParent) {
+ pParent->popChild(mpItemAlias);
+ }
+ } else {
+ qDebug() << "parent is null ";
+ }
+}
+
+void PasteAliasCommand::redo()
+{
+ if (!mpItem) {
+ auto parent = mpTreeWidgetAliases->currentIndex().parent();
+ auto parentRow = parent.row();
+
+ mSiblingRow = mpTreeWidgetAliases->currentIndex().row() + 1;
+ mpItemAlias = mpHost->getAliasUnit()->getAlias(mImportedItemID);
+ const int parentId = mpParent->data(0, Qt::UserRole).toInt();
+ mpHost->getAliasUnit()->reParentAlias(mImportedItemID, 0, parentId, parentRow, mSiblingRow);
+
+ mpEditor->selectAliasByID(mImportedItemID);
+ mpItem = mpTreeWidgetAliases->currentItem();
+ mpParent = mpItem->parent();
+ } else {
+ const int parentId = mpParent->data(0, Qt::UserRole).toInt();
+ TAlias* pParent = mpHost->getAliasUnit()->getAlias(parentId);
+ const int id = mpItem->data(0, Qt::UserRole).toInt();
+ TAlias* pT = mpHost->getAliasUnit()->getAlias(id);
+ mpItemAlias = pT;
+
+ if (pParent) {
+ mpParent->insertChild(mSiblingRow, mpItem);
+ } else {
+ mpParent->addChild(mpItem);
+ }
+ if (pParent) {
+ pParent->addChild(mpItemAlias);
+ }
+
+ mpEditor->selectAliasByID(id);
+ }
+
+ setText(QObject::tr("Paste alias"));
+}
+
+DeleteAliasCommand::DeleteAliasCommand(Host* pHost, QTreeWidgetItem* pItem, TTreeWidget* treeWidget_aliases, QUndoCommand* parent) : QUndoCommand(parent), mpHost(pHost)
+{
+ mpItem = pItem;
+ mpTreeWidgetAliases = treeWidget_aliases;
+}
+
+void DeleteAliasCommand::undo()
+{
+ if (!mpItem) {
+ return;
+ }
+ mpEditor->selectAliasByID(mParentID);
+ auto mParent = mpTreeWidgetAliases->currentItem();
+ mpParent = mParent;
+ if (mpParent) {
+ QList<QTreeWidgetItem*> childList;
+ mpTreeWidgetAliases->getAllChildren(mpItem, childList);
+ if (mpItemAliasList.size() != childList.size()) {
+ return;
+ }
+ for (int i = 0; i < childList.size(); ++i) {
+ mpItemAliasList[i]->setTemporary(false);
+ mpItemAliasList[i]->registerAlias();
+ childList[i]->setData(0, Qt::UserRole, mpItemAliasList[i]->getID());
+ }
+ mpParent->insertChild(mSiblingRow, mpItem);
+
+ const int itemId = mpItem->data(0, Qt::UserRole).toInt();
+
+ const int parentId = mpParent->data(0, Qt::UserRole).toInt();
+ mpHost->getAliasUnit()->reParentAlias(itemId, -1, parentId, mParentRow, mSiblingRow);
+ mpEditor->selectAliasByID(itemId);
+ mpItem = mpTreeWidgetAliases->currentItem();
+ } else {
+ qDebug() << "parent is null ";
+ }
+}
+
+void DeleteAliasCommand::redo()
+{
+ if (!mpEditor || !mpHost) {
+ return;
+ }
+ if (mpItem) {
+ auto parent = mCurrentIndex.parent();
+ auto parentRow = parent.row();
+ auto parentId = parent.data(Qt::UserRole).toInt();
+ mParentID = parentId;
+ const int siblingRow = mCurrentIndex.row();
+ mParentRow = parentRow;
+ mSiblingRow = siblingRow;
+ int itemId = mpItem->data(0, Qt::UserRole).toInt();
+ mpParent = mpItem->parent();
+ mpEditor->selectAliasByID(itemId);
+ mpItemAliasList.clear();
+ recurseAliases(mpItem, mpHost);
+ mpEditor->delete_alias();
+ }
+ setText(QObject::tr("Delete alias"));
+}
+
+void DeleteAliasCommand::recurseAliases(QTreeWidgetItem* mpItem, QPointer<Host> mpHost)
+{
+ TAlias* ptr = nullptr;
+ QList<QTreeWidgetItem*> childList;
+ mpTreeWidgetAliases->getAllChildren(mpItem, childList);
+ for (int i = 0; i < childList.size(); ++i) {
+ const int pID = childList[i]->data(0, Qt::UserRole).toInt();
+ TAlias* pChild = mpHost->getAliasUnit()->getAlias(pID);
+ if (pChild->getParent() && mpItemAliasList.size() > 0) {
+ for (int j = 0; j < mpItemAliasList.size(); j++) {
+ if (mpItemAliasList[j]->mID == pChild->getParent()->mID) {
+ ptr = new TAlias(mpItemAliasList[j], mpHost);
+ break;
+ }
+ }
+ } else {
+ ptr = new TAlias(pChild->mName, mpHost);
+ }
+ ptr->setID(pChild->mID);
+ ptr->setName(pChild->mName);
+ ptr->setCommand(pChild->getCommand());
+ ptr->setIsFolder(pChild->isFolder());
+ ptr->setIsActive(pChild->isActive());
+ ptr->setRegexCode(pChild->getRegexCode());
+ ptr->setTemporary(true);
+ mpItemAliasList.append(ptr);
+ }
+}
+
+MoveAliasCommand::MoveAliasCommand(Host* pHost,
+ TTreeWidget* treeWidget_aliases,
+ int childID,
+ int oldParentID,
+ int newParentID,
+ int parentPosition,
+ int childPosition,
+ int prevParentPosition,
+ int prevChildPosition,
+ QUndoCommand* parent)
+: QUndoCommand(parent), mpHost(pHost)
+{
+ mChildID = childID;
+ mOldParentID = oldParentID;
+ mNewParentID = newParentID;
+ mParentPosition = parentPosition;
+ mChildPosition = childPosition;
+ mPrevParentPosition = prevParentPosition;
+ mPrevChildPosition = prevChildPosition;
+ mpTreeWidgetAliases = treeWidget_aliases;
+}
+
+void MoveAliasCommand::undo()
+{
+ if (!mpHost) {
+ return;
+ }
+
+ mpEditor->selectAliasByID(mItemID);
+ mpItem = mpTreeWidgetAliases->currentItem();
+
+ mpEditor->selectAliasByID(mOldParentID);
+ mpPrevParentItem = mpTreeWidgetAliases->currentItem();
+
+ mpEditor->selectAliasByID(mNewParentID);
+ mpParentItem = mpTreeWidgetAliases->currentItem();
+
+ mpHost->getAliasUnit()->reParentAlias(mChildID, mNewParentID, mOldParentID, mPrevParentPosition, mPrevChildPosition);
+ mpParentItem->removeChild(mpItem);
+
+ mpPrevParentItem->insertChild(mPrevChildPosition, mpItem);
+
+ mpTreeWidgetAliases->setCurrentItem(mpItem);
+}
+
+void MoveAliasCommand::redo()
+{
+ if (!mpEditor) {
+ return;
+ }
+ mItemID = mpItem->data(0, Qt::UserRole).toInt();
+ mpHost->getAliasUnit()->reParentAlias(mChildID, mOldParentID, mNewParentID, mParentPosition, mChildPosition);
+ if (mpPrevParentItem) {
+ mpPrevParentItem->removeChild(mpItem);
+ }
+ if (mpParentItem) {
+ const int count = mpParentItem->childCount();
+ mpParentItem->insertChild(count <= 0 ? 0 : count, mpItem);
+ }
+
+ setText(QObject::tr("Move alias"));
+}
+
+ActiveToggleAliasEditedCommand::ActiveToggleAliasEditedCommand(Host* pHost, dlgAliasMainArea* triggersMainArea, QUndoCommand* parent) : QUndoCommand(parent), mpHost(pHost)
+{
+ mpAliasMainArea = triggersMainArea;
+}
+
+void ActiveToggleAliasEditedCommand::undo()
+{
+ if (!mpItem) {
+ return;
+ }
+
+ mpEditor->selectAliasByID(mItemID);
+ mpItem = mpTreeWidgetAliases->currentItem();
+ QIcon icon;
+ QString itemDescription;
+
+ mpItemAlias->setIsActive(mPrevAliasToggle);
+
+ if (mpItemAlias->isFolder()) {
+ if (mpItemAlias->isActive()) {
+ icon.addPixmap(QPixmap(qsl(":/icons/folder-violet.png")), QIcon::Normal, QIcon::Off);
+ itemDescription = descActiveFolder;
+ } else {
+ icon.addPixmap(QPixmap(qsl(":/icons/folder-violet-locked.png")), QIcon::Normal, QIcon::Off);
+ itemDescription = descInactiveFolder;
+ }
+ } else {
+ if (mpItemAlias->isActive()) {
+ itemDescription = descActive;
+ if (mpItemAlias->ancestorsActive()) {
+ icon.addPixmap(QPixmap(qsl(":/icons/tag_checkbox_checked.png")), QIcon::Normal, QIcon::Off);
+ } else {
+ icon.addPixmap(QPixmap(qsl(":/icons/tag_checkbox_checked_grey.png")), QIcon::Normal, QIcon::Off);
+ itemDescription = descInactiveParent.arg(itemDescription);
+ }
+ } else {
+ icon.addPixmap(QPixmap(qsl(":/icons/tag_checkbox.png")), QIcon::Normal, QIcon::Off);
+ itemDescription = descInactive;
+ }
+ }
+
+ if (!mpItemAlias->state()) {
+ mpItemAlias->setIsActive(false);
+ mpEditor->showError(mpEditor->tr(R"(<p><b>Unable to activate "<tt>%1</tt>"; %2.</b></p>
+ <p><i>You will need to reactivate this after the problem has been corrected.</i></p>)")
+ .arg(mpItemAlias->getName().toHtmlEscaped(), mpItemAlias->getError()));
+ icon.addPixmap(QPixmap(qsl(":/icons/tools-report-bug.png")), QIcon::Normal, QIcon::Off);
+ itemDescription = descError;
+ }
+ mpItem->setIcon(0, icon);
+ mpItem->setText(0, mpItemAlias->getName());
+ mpItem->setData(0, Qt::AccessibleDescriptionRole, itemDescription);
+
+ if (mpItem->childCount() > 0) {
+ mpEditor->children_icon_alias(mpItem);
+ }
+}
+
+void ActiveToggleAliasEditedCommand::redo()
+{
+ if (!mpItem) {
+ return;
+ }
+ mItemID = mpItem->data(0, Qt::UserRole).toInt();
+ mpEditor->selectAliasByID(mItemID);
+ mpItem = mpTreeWidgetAliases->currentItem();
+ QIcon icon;
+ QString itemDescription;
+ // bool prevTriggerToggle = mpItemTrigger->isActive();
+ mpItemAlias->setIsActive(mAliasToggle);
+
+ if (mpItemAlias->isFolder()) {
+ if (mpItemAlias->isActive()) {
+ icon.addPixmap(QPixmap(qsl(":/icons/folder-violet.png")), QIcon::Normal, QIcon::Off);
+ itemDescription = descActiveFolder;
+ } else {
+ icon.addPixmap(QPixmap(qsl(":/icons/folder-violet-locked.png")), QIcon::Normal, QIcon::Off);
+ itemDescription = descInactiveFolder;
+ }
+ } else {
+ if (mpItemAlias->isActive()) {
+ itemDescription = descActive;
+ if (mpItemAlias->ancestorsActive()) {
+ icon.addPixmap(QPixmap(qsl(":/icons/tag_checkbox_checked.png")), QIcon::Normal, QIcon::Off);
+ } else {
+ icon.addPixmap(QPixmap(qsl(":/icons/tag_checkbox_checked_grey.png")), QIcon::Normal, QIcon::Off);
+ itemDescription = descInactiveParent.arg(itemDescription);
+ }
+ } else {
+ icon.addPixmap(QPixmap(qsl(":/icons/tag_checkbox.png")), QIcon::Normal, QIcon::Off);
+ itemDescription = descInactive;
+ }
+ }
+
+ if (!mpItemAlias->state()) {
+ mpItemAlias->setIsActive(false);
+ mpEditor->showError(mpItemAlias
+ ->tr(R"(<p><b>Unable to activate "<tt>%1</tt>"; %2.</b></p>
+ <p><i>You will need to reactivate this after the problem has been corrected.</i></p>)")
+ .arg(mpItemAlias->getName().toHtmlEscaped(), mpItemAlias->getError()));
+ icon.addPixmap(QPixmap(qsl(":/icons/tools-report-bug.png")), QIcon::Normal, QIcon::Off);
+ itemDescription = descError;
+ }
+ mpItem->setIcon(0, icon);
+ mpItem->setText(0, mpItemAlias->getName());
+ mpItem->setData(0, Qt::AccessibleDescriptionRole, itemDescription);
+
+ if (mpItem->childCount() > 0) {
+ mpEditor->children_icon_alias(mpItem);
+ }
+ // mpEditor->slot_saveSelectedItem(mpItem);
+ setText(QObject::tr("Edit alias toggle"));
+}
+
+AliasNameTextEditedCommand::AliasNameTextEditedCommand(Host* pHost, dlgAliasMainArea* aliasMainArea, QUndoCommand* parent) : QUndoCommand(parent), mpHost(pHost)
+{
+ mpAliasMainArea = aliasMainArea;
+}
+
+void AliasNameTextEditedCommand::undo()
+{
+ if (!mpItem) {
+ return;
+ }
+
+ mpEditor->selectAliasByID(mItemID);
+ mpItem = mpTreeWidgetAliases->currentItem();
+
+ mpAliasMainArea->lineEdit_alias_name->blockSignals(true);
+ mpAliasMainArea->lineEdit_alias_name->setText(mPrevAliasName);
+ mpAliasMainArea->lineEdit_alias_name->blockSignals(false);
+ mpEditor->slot_saveSelectedItem(mpItem);
+}
+
+void AliasNameTextEditedCommand::redo()
+{
+ if (!mpItem) {
+ return;
+ }
+ mItemID = mpItem->data(0, Qt::UserRole).toInt();
+ mpEditor->selectAliasByID(mItemID);
+ mpItem = mpTreeWidgetAliases->currentItem();
+
+ mpAliasMainArea->lineEdit_alias_name->blockSignals(true);
+ mpAliasMainArea->lineEdit_alias_name->setText(mAliasName);
+ mpAliasMainArea->lineEdit_alias_name->blockSignals(false);
+ mpEditor->slot_saveSelectedItem(mpItem);
+ setText(QObject::tr("Edit alias name"));
+}
+
+AliasCommandTextEditedCommand::AliasCommandTextEditedCommand(Host* pHost, dlgAliasMainArea* aliasMainArea, QUndoCommand* parent) : QUndoCommand(parent), mpHost(pHost)
+{
+ mpAliasMainArea = aliasMainArea;
+}
+
+void AliasCommandTextEditedCommand::undo()
+{
+ if (!mpItem) {
+ return;
+ }
+
+ mpEditor->selectAliasByID(mItemID);
+ mpItem = mpTreeWidgetAliases->currentItem();
+
+ mpAliasMainArea->lineEdit_alias_command->setText(mPrevAliasCommand);
+ mpEditor->slot_saveSelectedItem(mpItem);
+}
+
+void AliasCommandTextEditedCommand::redo()
+{
+ if (!mpItem) {
+ return;
+ }
+
+ mItemID = mpItem->data(0, Qt::UserRole).toInt();
+ mpEditor->selectAliasByID(mItemID);
+ mpItem = mpTreeWidgetAliases->currentItem();
+
+ mpAliasMainArea->lineEdit_alias_command->blockSignals(true);
+ mpAliasMainArea->lineEdit_alias_command->setText(mAliasCommand);
+ mpAliasMainArea->lineEdit_alias_command->blockSignals(false);
+ mpEditor->slot_saveSelectedItem(mpItem);
+ setText(QObject::tr("Edit alias command"));
+}
+
+AliasPatternTextEditedCommand::AliasPatternTextEditedCommand(Host* pHost, dlgAliasMainArea* aliasMainArea, QUndoCommand* parent) : QUndoCommand(parent), mpHost(pHost)
+{
+ mpAliasMainArea = aliasMainArea;
+}
+
+void AliasPatternTextEditedCommand::undo()
+{
+ if (!mpItem) {
+ return;
+ }
+
+ mpEditor->selectAliasByID(mItemID);
+ mpItem = mpTreeWidgetAliases->currentItem();
+
+ mpAliasMainArea->lineEdit_alias_pattern->blockSignals(true);
+ mpAliasMainArea->lineEdit_alias_pattern->setText(mPrevAliasPattern);
+ mpAliasMainArea->lineEdit_alias_pattern->blockSignals(false);
+ mpEditor->slot_saveSelectedItem(mpItem);
+}
+
+void AliasPatternTextEditedCommand::redo()
+{
+ if (!mpItem) {
+ return;
+ }
+
+ mItemID = mpItem->data(0, Qt::UserRole).toInt();
+ mpEditor->selectAliasByID(mItemID);
+ mpItem = mpTreeWidgetAliases->currentItem();
+
+ mpAliasMainArea->lineEdit_alias_pattern->blockSignals(true);
+ mpAliasMainArea->lineEdit_alias_pattern->setText(mAliasPattern);
+ mpAliasMainArea->lineEdit_alias_pattern->blockSignals(false);
+ setText(QObject::tr("Edit alias pattern"));
+ mpEditor->slot_saveSelectedItem(mpItem);
+}
+
+AddTimerCommand::AddTimerCommand(Host* pHost, TTreeWidget* treeWidgetTimers, bool isFolder, QUndoCommand* parent) : QUndoCommand(parent), mpHost(pHost)
+{
+ mpTreeWidgetTimers = treeWidgetTimers;
+ mIsFolder = isFolder;
+}
+
+void AddTimerCommand::undo()
+{
+ if (!mpItem) {
+ return;
+ }
+
+ mpEditor->selectTimerByID(mItemID);
+ mpItem = mpTreeWidgetTimers->currentItem();
+ const int id = mpItem->data(0, Qt::UserRole).toInt();
+ TTimer* pT = mpHost->getTimerUnit()->getTimer(id);
+ mpItemTimer = pT;
+
+ mpParent = mpItem->parent();
+
+ if (mpParent) {
+ mpParent->removeChild(mpItem);
+ const int parentId = mpParent->data(0, Qt::UserRole).toInt();
+ TTimer* pParent = mpHost->getTimerUnit()->getTimer(parentId);
+ if (pParent) {
+ pParent->popChild(mpItemTimer);
+ }
+ } else {
+ qDebug() << "parent is null ";
+ }
+}
+
+void AddTimerCommand::redo()
+{
+ if (!mpEditor) {
+ return;
+ }
+ if (!mpItem) {
+ mpEditor->addTimer(mIsFolder);
+ mpItem = mpTreeWidgetTimers->currentItem();
+ mCurrentIndex = mpTreeWidgetTimers->currentIndex();
+ mSiblingRow = mCurrentIndex.row();
+ mpParent = mpItem->parent();
+ mItemID = mpItem->data(0, Qt::UserRole).toInt();
+ } else {
+ const int parentId = mpParent->data(0, Qt::UserRole).toInt();
+ TTimer* pParent = mpHost->getTimerUnit()->getTimer(parentId);
+ mpItemTimer->setTemporary(false);
+ if (mIsFolder) {
+ mpParent->addChild(mpItem);
+ } else {
+ mpParent->insertChild(mSiblingRow, mpItem);
+ }
+ if (pParent) {
+ TTimer* pT = mpHost->getTimerUnit()->getTimer(mItemID);
+ pParent->addChild(pT);
+ }
+ }
+ setText(QObject::tr("Add timer"));
+}
+
+PasteTimerCommand::PasteTimerCommand(Host* pHost, TTreeWidget* treeWidgetTimers, QUndoCommand* parent) : QUndoCommand(parent), mpHost(pHost)
+{
+ mpTreeWidgetTimers = treeWidgetTimers;
+}
+
+void PasteTimerCommand::undo()
+{
+ if (!mpEditor) {
+ return;
+ }
+
+ mpEditor->selectTimerByID(mImportedItemID);
+ mpItem = mpTreeWidgetTimers->currentItem();
+ TTimer* pT = mpHost->getTimerUnit()->getTimer(mImportedItemID);
+ mpItemTimer = pT;
+ mpParent = mpItem->parent();
+ if (mpParent) {
+ mpParent->removeChild(mpItem);
+ const int parentId = mpParent->data(0, Qt::UserRole).toInt();
+ TTimer* pParent = mpHost->getTimerUnit()->getTimer(parentId);
+ if (pParent) {
+ pParent->popChild(mpItemTimer);
+ }
+ } else {
+ qDebug() << "parent is null ";
+ }
+}
+
+void PasteTimerCommand::redo()
+{
+ if (!mpItem) {
+ auto parent = mpTreeWidgetTimers->currentIndex().parent();
+ auto parentRow = parent.row();
+
+ mSiblingRow = mpTreeWidgetTimers->currentIndex().row() + 1;
+ mpItemTimer = mpHost->getTimerUnit()->getTimer(mImportedItemID);
+ const int parentId = mpParent->data(0, Qt::UserRole).toInt();
+ mpHost->getTimerUnit()->reParentTimer(mImportedItemID, 0, parentId, parentRow, mSiblingRow);
+
+ mpEditor->selectTimerByID(mImportedItemID);
+ mpItem = mpTreeWidgetTimers->currentItem();
+ mpParent = mpItem->parent();
+ } else {
+ const int parentId = mpParent->data(0, Qt::UserRole).toInt();
+ TTimer* pParent = mpHost->getTimerUnit()->getTimer(parentId);
+ const int id = mpItem->data(0, Qt::UserRole).toInt();
+ TTimer* pT = mpHost->getTimerUnit()->getTimer(id);
+ mpItemTimer = pT;
+
+ if (pParent) {
+ mpParent->insertChild(mSiblingRow, mpItem);
+ } else {
+ mpParent->addChild(mpItem);
+ }
+ if (pParent) {
+ pParent->addChild(mpItemTimer);
+ }
+
+ mpEditor->selectTimerByID(id);
+ }
+
+ setText(QObject::tr("Paste timer"));
+}
+
+DeleteTimerCommand::DeleteTimerCommand(Host* pHost, QTreeWidgetItem* pItem, TTreeWidget* treeWidgetTimers, QUndoCommand* parent) : QUndoCommand(parent), mpHost(pHost)
+{
+ mpItem = pItem;
+ mpTreeWidgetTimers = treeWidgetTimers;
+}
+
+void DeleteTimerCommand::undo()
+{
+ if (!mpItem) {
+ return;
+ }
+ mpEditor->selectTimerByID(mParentID);
+ auto mParent = mpTreeWidgetTimers->currentItem();
+ mpParent = mParent;
+ if (mpParent) {
+ QList<QTreeWidgetItem*> childList;
+ mpTreeWidgetTimers->getAllChildren(mpItem, childList);
+ if (mpItemTimerList.size() != childList.size()) {
+ return;
+ }
+ for (int i = 0; i < childList.size(); ++i) {
+ mpItemTimerList[i]->setTemporary(false);
+ mpHost->getTimerUnit()->registerTimer(mpItemTimerList[i]);
+ childList[i]->setData(0, Qt::UserRole, mpItemTimerList[i]->getID());
+ }
+ mpParent->insertChild(mSiblingRow, mpItem);
+
+ const int itemId = mpItem->data(0, Qt::UserRole).toInt();
+ const int parentId = mpParent->data(0, Qt::UserRole).toInt();
+ mpHost->getTimerUnit()->reParentTimer(itemId, -1, parentId, mParentRow, mSiblingRow);
+ mpEditor->selectTimerByID(itemId);
+ mpItem = mpTreeWidgetTimers->currentItem();
+ } else {
+ qDebug() << "parent is null ";
+ }
+}
+
+void DeleteTimerCommand::redo()
+{
+ if (!mpEditor || !mpHost) {
+ return;
+ }
+ if (mpItem) {
+ auto parent = mCurrentIndex.parent();
+ auto parentRow = parent.row();
+ auto parentId = parent.data(Qt::UserRole).toInt();
+ mParentID = parentId;
+ const int siblingRow = mCurrentIndex.row();
+ mParentRow = parentRow;
+ mSiblingRow = siblingRow;
+ int itemId = mpItem->data(0, Qt::UserRole).toInt();
+ mpParent = mpItem->parent();
+ mpEditor->selectTimerByID(itemId);
+ mpItemTimerList.clear();
+ recurseTimers(mpItem, mpHost);
+ mpEditor->delete_timer();
+ }
+ setText(QObject::tr("Delete timer"));
+}
+
+void DeleteTimerCommand::recurseTimers(QTreeWidgetItem* mpItem, QPointer<Host> mpHost)
+{
+ TTimer* ptr = nullptr;
+ QList<QTreeWidgetItem*> childList;
+ mpTreeWidgetTimers->getAllChildren(mpItem, childList);
+ for (int i = 0; i < childList.size(); ++i) {
+ const int pID = childList[i]->data(0, Qt::UserRole).toInt();
+ TTimer* pChild = mpHost->getTimerUnit()->getTimer(pID);
+ if (pChild->getParent() && mpItemTimerList.size() > 0) {
+ for (int j = 0; j < mpItemTimerList.size(); j++) {
+ if (mpItemTimerList[j]->mID == pChild->getParent()->mID) {
+ ptr = new TTimer(mpItemTimerList[j], mpHost);
+ break;
+ }
+ }
+ } else {
+ ptr = new TTimer(pChild->getName(), pChild->getTime(), mpHost);
+ }
+ ptr->setID(pChild->mID);
+ ptr->setIsFolder(pChild->isFolder());
+ ptr->setIsActive(pChild->isActive());
+ ptr->setName(pChild->getName());
+ ptr->setCommand(pChild->getCommand());
+ ptr->setTime(pChild->getTime());
+ ptr->setTemporary(true);
+ mpItemTimerList.append(ptr);
+ }
+}
+
+MoveTimerCommand::MoveTimerCommand(Host* pHost,
+ TTreeWidget* treeWidgetTimers,
+ int childID,
+ int oldParentID,
+ int newParentID,
+ int parentPosition,
+ int childPosition,
+ int prevParentPosition,
+ int prevChildPosition,
+ QUndoCommand* parent)
+: QUndoCommand(parent), mpHost(pHost)
+{
+ mChildID = childID;
+ mOldParentID = oldParentID;
+ mNewParentID = newParentID;
+ mParentPosition = parentPosition;
+ mChildPosition = childPosition;
+ mPrevParentPosition = prevParentPosition;
+ mPrevChildPosition = prevChildPosition;
+ mpTreeWidgetTimers = treeWidgetTimers;
+}
+
+void MoveTimerCommand::undo()
+{
+ if (!mpHost) {
+ return;
+ }
+
+ mpEditor->selectTimerByID(mItemID);
+ mpItem = mpTreeWidgetTimers->currentItem();
+
+ mpEditor->selectTimerByID(mOldParentID);
+ mpPrevParentItem = mpTreeWidgetTimers->currentItem();
+
+ mpEditor->selectTimerByID(mNewParentID);
+ mpParentItem = mpTreeWidgetTimers->currentItem();
+
+ mpHost->getTimerUnit()->reParentTimer(mChildID, mNewParentID, mOldParentID, mPrevParentPosition, mPrevChildPosition);
+ mpParentItem->removeChild(mpItem);
+
+ mpPrevParentItem->insertChild(mPrevChildPosition, mpItem);
+
+ mpTreeWidgetTimers->setCurrentItem(mpItem);
+}
+
+void MoveTimerCommand::redo()
+{
+ if (!mpEditor) {
+ return;
+ }
+ mItemID = mpItem->data(0, Qt::UserRole).toInt();
+ mpHost->getTimerUnit()->reParentTimer(mChildID, mOldParentID, mNewParentID, mParentPosition, mChildPosition);
+ if (mpPrevParentItem) {
+ mpPrevParentItem->removeChild(mpItem);
+ }
+ if (mpParentItem) {
+ const int count = mpParentItem->childCount();
+ mpParentItem->insertChild(count <= 0 ? 0 : count, mpItem);
+ }
+
+ setText(QObject::tr("Move timer"));
+}
+
+ActiveToggleTimerEditedCommand::ActiveToggleTimerEditedCommand(Host* pHost, dlgTimersMainArea* timersMainArea, QUndoCommand* parent) : QUndoCommand(parent), mpHost(pHost)
+{
+ mpTimersMainArea = timersMainArea;
+}
+
+void ActiveToggleTimerEditedCommand::undo()
+{
+ if (!mpItem) {
+ return;
+ }
+
+ mpEditor->selectTimerByID(mItemID);
+ mpItem = mpTreeWidgetTimers->currentItem();
+ QIcon icon;
+ QString itemDescription;
+
+ if (!mpItemTimer->isOffsetTimer()) {
+ mpItemTimer->setIsActive(mPrevTimerToggle);
+ } else {
+ mpItemTimer->setShouldBeActive(mPrevTimerToggle);
+ }
+
+ if (mpItemTimer->isFolder()) {
+ // disable or enable all timers in the respective branch
+ // irrespective of the user defined state.
+ if (mpItemTimer->shouldBeActive()) {
+ mpItemTimer->enableTimer(mpItemTimer->getID());
+ } else {
+ mpItemTimer->disableTimer(mpItemTimer->getID());
+ }
+
+ if (mpItemTimer->shouldBeActive()) {
+ itemDescription = descActiveFolder;
+ if (mpItemTimer->ancestorsActive()) {
+ if (!mpItemTimer->mPackageName.isEmpty()) {
+ icon.addPixmap(QPixmap(qsl(":/icons/folder-brown.png")), QIcon::Normal, QIcon::Off);
+ } else {
+ icon.addPixmap(QPixmap(qsl(":/icons/folder-green.png")), QIcon::Normal, QIcon::Off);
+ }
+ } else {
+ icon.addPixmap(QPixmap(qsl(":/icons/folder-grey.png")), QIcon::Normal, QIcon::Off);
+ itemDescription = descInactiveParent.arg(itemDescription);
+ }
+ } else {
+ itemDescription = descInactiveFolder;
+ if (mpItemTimer->ancestorsActive()) {
+ if (!mpItemTimer->mPackageName.isEmpty()) {
+ icon.addPixmap(QPixmap(qsl(":/icons/folder-brown-locked.png")), QIcon::Normal, QIcon::Off);
+ } else {
+ icon.addPixmap(QPixmap(qsl(":/icons/folder-green-locked.png")), QIcon::Normal, QIcon::Off);
+ }
+ } else {
+ icon.addPixmap(QPixmap(qsl(":/icons/folder-grey-locked.png")), QIcon::Normal, QIcon::Off);
+ }
+ }
+ } else {
+ if (mpItemTimer->isOffsetTimer()) {
+ // state of offset timers is managed by the trigger engine
+ if (mpItemTimer->shouldBeActive()) {
+ mpItemTimer->enableTimer(mpItemTimer->getID());
+ itemDescription = descActiveOffsetTimer;
+ if (mpItemTimer->ancestorsActive()) {
+ icon.addPixmap(QPixmap(qsl(":/icons/offsettimer-on.png")), QIcon::Normal, QIcon::Off);
+ } else {
+ icon.addPixmap(QPixmap(qsl(":/icons/offsettimer-on-grey.png")), QIcon::Normal, QIcon::Off);
+ itemDescription = descInactiveParent.arg(itemDescription);
+ }
+ } else {
+ mpItemTimer->disableTimer(mpItemTimer->getID());
+ itemDescription = descInactiveOffsetTimer;
+ if (mpItemTimer->ancestorsActive()) {
+ icon.addPixmap(QPixmap(qsl(":/icons/offsettimer-off.png")), QIcon::Normal, QIcon::Off);
+ } else {
+ icon.addPixmap(QPixmap(qsl(":/icons/offsettimer-off-grey.png")), QIcon::Normal, QIcon::Off);
+ }
+ }
+ } else {
+ if (mpItemTimer->shouldBeActive()) {
+ mpItemTimer->enableTimer(mpItemTimer->getID());
+ itemDescription = descActive;
+ if (mpItemTimer->ancestorsActive()) {
+ icon.addPixmap(QPixmap(qsl(":/icons/tag_checkbox_checked.png")), QIcon::Normal, QIcon::Off);
+ } else {
+ icon.addPixmap(QPixmap(qsl(":/icons/tag_checkbox_checked_grey.png")), QIcon::Normal, QIcon::Off);
+ itemDescription = descInactiveParent.arg(itemDescription);
+ }
+ } else {
+ mpItemTimer->disableTimer(mpItemTimer->getID());
+ icon.addPixmap(QPixmap(qsl(":/icons/tag_checkbox.png")), QIcon::Normal, QIcon::Off);
+ itemDescription = descInactive;
+ }
+ }
+ }
+
+ if (!mpItemTimer->state()) {
+ mpItemTimer->setIsActive(false);
+ mpEditor->showError(mpEditor->tr(R"(<p><b>Unable to activate "<tt>%1</tt>": %2.</b></p>
+ <p><i>You will need to reactivate this after the problem has been corrected.</i></p>)")
+ .arg(mpItemTimer->getName().toHtmlEscaped(), mpItemTimer->getError()));
+ icon.addPixmap(QPixmap(qsl(":/icons/tools-report-bug.png")), QIcon::Normal, QIcon::Off);
+ itemDescription = descError;
+ }
+ mpItem->setIcon(0, icon);
+ mpItem->setText(0, mpItemTimer->getName());
+ mpItem->setData(0, Qt::AccessibleDescriptionRole, itemDescription);
+
+ if (mpItem->childCount() > 0) {
+ mpEditor->children_icon_timer(mpItem);
+ }
+ // mpEditor->slot_saveSelectedItem(mpItem);
+}
+
+void ActiveToggleTimerEditedCommand::redo()
+{
+ if (!mpItem) {
+ return;
+ }
+ mItemID = mpItem->data(0, Qt::UserRole).toInt();
+ mpEditor->selectTimerByID(mItemID);
+ mpItem = mpTreeWidgetTimers->currentItem();
+ QIcon icon;
+ QString itemDescription;
+
+ if (!mpItemTimer->isOffsetTimer()) {
+ mpItemTimer->setIsActive(mTimerToggle);
+ } else {
+ mpItemTimer->setShouldBeActive(mTimerToggle);
+ }
+
+ if (mpItemTimer->isFolder()) {
+ // disable or enable all timers in the respective branch
+ // irrespective of the user defined state.
+ if (mpItemTimer->shouldBeActive()) {
+ mpItemTimer->enableTimer(mpItemTimer->getID());
+ } else {
+ mpItemTimer->disableTimer(mpItemTimer->getID());
+ }
+
+ if (mpItemTimer->shouldBeActive()) {
+ itemDescription = descActiveFolder;
+ if (mpItemTimer->ancestorsActive()) {
+ if (!mpItemTimer->mPackageName.isEmpty()) {
+ icon.addPixmap(QPixmap(qsl(":/icons/folder-brown.png")), QIcon::Normal, QIcon::Off);
+ } else {
+ icon.addPixmap(QPixmap(qsl(":/icons/folder-green.png")), QIcon::Normal, QIcon::Off);
+ }
+ } else {
+ icon.addPixmap(QPixmap(qsl(":/icons/folder-grey.png")), QIcon::Normal, QIcon::Off);
+ itemDescription = descInactiveParent.arg(itemDescription);
+ }
+ } else {
+ itemDescription = descInactiveFolder;
+ if (mpItemTimer->ancestorsActive()) {
+ if (!mpItemTimer->mPackageName.isEmpty()) {
+ icon.addPixmap(QPixmap(qsl(":/icons/folder-brown-locked.png")), QIcon::Normal, QIcon::Off);
+ } else {
+ icon.addPixmap(QPixmap(qsl(":/icons/folder-green-locked.png")), QIcon::Normal, QIcon::Off);
+ }
+ } else {
+ icon.addPixmap(QPixmap(qsl(":/icons/folder-grey-locked.png")), QIcon::Normal, QIcon::Off);
+ }
+ }
+ } else {
+ if (mpItemTimer->isOffsetTimer()) {
+ // state of offset timers is managed by the trigger engine
+ if (mpItemTimer->shouldBeActive()) {
+ mpItemTimer->enableTimer(mpItemTimer->getID());
+ itemDescription = descActiveOffsetTimer;
+ if (mpItemTimer->ancestorsActive()) {
+ icon.addPixmap(QPixmap(qsl(":/icons/offsettimer-on.png")), QIcon::Normal, QIcon::Off);
+ } else {
+ icon.addPixmap(QPixmap(qsl(":/icons/offsettimer-on-grey.png")), QIcon::Normal, QIcon::Off);
+ itemDescription = descInactiveParent.arg(itemDescription);
+ }
+ } else {
+ mpItemTimer->disableTimer(mpItemTimer->getID());
+ itemDescription = descInactiveOffsetTimer;
+ if (mpItemTimer->ancestorsActive()) {
+ icon.addPixmap(QPixmap(qsl(":/icons/offsettimer-off.png")), QIcon::Normal, QIcon::Off);
+ } else {
+ icon.addPixmap(QPixmap(qsl(":/icons/offsettimer-off-grey.png")), QIcon::Normal, QIcon::Off);
+ }
+ }
+ } else {
+ if (mpItemTimer->shouldBeActive()) {
+ mpItemTimer->enableTimer(mpItemTimer->getID());
+ itemDescription = descActive;
+ if (mpItemTimer->ancestorsActive()) {
+ icon.addPixmap(QPixmap(qsl(":/icons/tag_checkbox_checked.png")), QIcon::Normal, QIcon::Off);
+ } else {
+ icon.addPixmap(QPixmap(qsl(":/icons/tag_checkbox_checked_grey.png")), QIcon::Normal, QIcon::Off);
+ itemDescription = descInactiveParent.arg(itemDescription);
+ }
+ } else {
+ mpItemTimer->disableTimer(mpItemTimer->getID());
+ icon.addPixmap(QPixmap(qsl(":/icons/tag_checkbox.png")), QIcon::Normal, QIcon::Off);
+ itemDescription = descInactive;
+ }
+ }
+ }
+
+ if (!mpItemTimer->state()) {
+ mpItemTimer->setIsActive(false);
+ mpEditor->showError(mpEditor->tr(R"(<p><b>Unable to activate "<tt>%1</tt>": %2.</b></p>
+ <p><i>You will need to reactivate this after the problem has been corrected.</i></p>)")
+ .arg(mpItemTimer->getName().toHtmlEscaped(), mpItemTimer->getError()));
+ icon.addPixmap(QPixmap(qsl(":/icons/tools-report-bug.png")), QIcon::Normal, QIcon::Off);
+ itemDescription = descError;
+ }
+ mpItem->setIcon(0, icon);
+ mpItem->setText(0, mpItemTimer->getName());
+ mpItem->setData(0, Qt::AccessibleDescriptionRole, itemDescription);
+
+ if (mpItem->childCount() > 0) {
+ mpEditor->children_icon_timer(mpItem);
+ }
+ // mpEditor->slot_saveSelectedItem(mpItem);
+ setText(QObject::tr("Edit timer toggle"));
+}
+
+TimerNameTextEditedCommand::TimerNameTextEditedCommand(Host* pHost, dlgTimersMainArea* timersMainArea, QUndoCommand* parent) : QUndoCommand(parent), mpHost(pHost)
+{
+ mpTimersMainArea = timersMainArea;
+}
+
+void TimerNameTextEditedCommand::undo()
+{
+ if (!mpItem) {
+ return;
+ }
+
+ mpEditor->selectTimerByID(mItemID);
+ mpItem = mpTreeWidgetTimers->currentItem();
+
+ mpTreeWidgetTimers->setCurrentItem(mpItem);
+ mpTimersMainArea->lineEdit_timer_name->blockSignals(true);
+ mpTimersMainArea->lineEdit_timer_name->setText(mPrevTimerName);
+ mpTimersMainArea->lineEdit_timer_name->blockSignals(false);
+ mpEditor->slot_saveSelectedItem(mpItem);
+}
+
+void TimerNameTextEditedCommand::redo()
+{
+ if (!mpItem) {
+ return;
+ }
+ mItemID = mpItem->data(0, Qt::UserRole).toInt();
+ mpEditor->selectTimerByID(mItemID);
+ mpItem = mpTreeWidgetTimers->currentItem();
+ mpTimersMainArea->lineEdit_timer_name->blockSignals(true);
+ mpTimersMainArea->lineEdit_timer_name->setText(mTimerName);
+ mpTimersMainArea->lineEdit_timer_name->blockSignals(false);
+ mpEditor->slot_saveSelectedItem(mpItem);
+ setText(QObject::tr("Edit timer name"));
+}
+
+TimerCommandTextEditedCommand::TimerCommandTextEditedCommand(Host* pHost, dlgTimersMainArea* timersMainArea, QUndoCommand* parent) : QUndoCommand(parent), mpHost(pHost)
+{
+ mpTimersMainArea = timersMainArea;
+}
+
+void TimerCommandTextEditedCommand::undo()
+{
+ if (!mpItem) {
+ return;
+ }
+
+ mpEditor->selectTimerByID(mItemID);
+ mpItem = mpTreeWidgetTimers->currentItem();
+ mpTimersMainArea->lineEdit_timer_command->blockSignals(true);
+ mpTimersMainArea->lineEdit_timer_command->setText(mPrevTimerCommand);
+ mpTimersMainArea->lineEdit_timer_command->blockSignals(false);
+}
+
+void TimerCommandTextEditedCommand::redo()
+{
+ if (!mpItem) {
+ return;
+ }
+ mItemID = mpItem->data(0, Qt::UserRole).toInt();
+ mpEditor->selectTimerByID(mItemID);
+ mpItem = mpTreeWidgetTimers->currentItem();
+ mpTimersMainArea->lineEdit_timer_command->blockSignals(true);
+ mpTimersMainArea->lineEdit_timer_command->setText(mTimerCommand);
+ mpTimersMainArea->lineEdit_timer_command->blockSignals(false);
+ setText(QObject::tr("Edit timer command"));
+}
+
+TimerHoursTextEditedCommand::TimerHoursTextEditedCommand(Host* pHost, dlgTimersMainArea* timersMainArea, QUndoCommand* parent) : QUndoCommand(parent), mpHost(pHost)
+{
+ mpTimersMainArea = timersMainArea;
+}
+
+void TimerHoursTextEditedCommand::undo()
+{
+ if (!mpItem) {
+ return;
+ }
+
+ mpEditor->selectTimerByID(mItemID);
+ mpItem = mpTreeWidgetTimers->currentItem();
+
+ mpTimersMainArea->timeEdit_timer_hours->blockSignals(true);
+ mpTimersMainArea->timeEdit_timer_hours->setTime(QTime(mPrevTimerHours, 0, 0, 0));
+ mpTimersMainArea->timeEdit_timer_hours->blockSignals(false);
+}
+
+void TimerHoursTextEditedCommand::redo()
+{
+ if (!mpItem) {
+ return;
+ }
+ mItemID = mpItem->data(0, Qt::UserRole).toInt();
+ mpEditor->selectTimerByID(mItemID);
+ mpItem = mpTreeWidgetTimers->currentItem();
+
+ mpTimersMainArea->timeEdit_timer_hours->blockSignals(true);
+ mpTimersMainArea->timeEdit_timer_hours->setTime(QTime(mTimerHours, 0, 0, 0));
+ mpTimersMainArea->timeEdit_timer_hours->blockSignals(false);
+ setText(QObject::tr("Edit timer hours"));
+}
+
+TimerMinutesTextEditedCommand::TimerMinutesTextEditedCommand(Host* pHost, dlgTimersMainArea* timersMainArea, QUndoCommand* parent) : QUndoCommand(parent), mpHost(pHost)
+{
+ mpTimersMainArea = timersMainArea;
+}
+
+void TimerMinutesTextEditedCommand::undo()
+{
+ if (!mpItem) {
+ return;
+ }
+
+ mpEditor->selectTimerByID(mItemID);
+ mpItem = mpTreeWidgetTimers->currentItem();
+
+ mpEditor->slot_timerSelected(mpItem);
+ mpTimersMainArea->timeEdit_timer_minutes->blockSignals(true);
+ mpTimersMainArea->timeEdit_timer_minutes->setTime(QTime(0, mPrevTimerMinutes, 0, 0));
+ mpTimersMainArea->timeEdit_timer_minutes->blockSignals(false);
+}
+
+void TimerMinutesTextEditedCommand::redo()
+{
+ if (!mpItem) {
+ return;
+ }
+ mItemID = mpItem->data(0, Qt::UserRole).toInt();
+ mpEditor->selectTimerByID(mItemID);
+ mpItem = mpTreeWidgetTimers->currentItem();
+
+ mpTimersMainArea->timeEdit_timer_minutes->blockSignals(true);
+ mpTimersMainArea->timeEdit_timer_minutes->setTime(QTime(0, mTimerMinutes, 0, 0));
+ mpTimersMainArea->timeEdit_timer_minutes->blockSignals(false);
+ setText(QObject::tr("Edit timer minutes"));
+}
+
+TimerSecondsTextEditedCommand::TimerSecondsTextEditedCommand(Host* pHost, dlgTimersMainArea* timersMainArea, QUndoCommand* parent) : QUndoCommand(parent), mpHost(pHost)
+{
+ mpTimersMainArea = timersMainArea;
+}
+
+void TimerSecondsTextEditedCommand::undo()
+{
+ if (!mpItem) {
+ return;
+ }
+
+ mpEditor->selectTimerByID(mItemID);
+ mpItem = mpTreeWidgetTimers->currentItem();
+
+ mpTimersMainArea->timeEdit_timer_seconds->blockSignals(true);
+ mpTimersMainArea->timeEdit_timer_seconds->setTime(QTime(0, 0, mPrevTimerSeconds, 0));
+ mpTimersMainArea->timeEdit_timer_seconds->blockSignals(false);
+}
+
+void TimerSecondsTextEditedCommand::redo()
+{
+ if (!mpItem) {
+ return;
+ }
+ mItemID = mpItem->data(0, Qt::UserRole).toInt();
+ mpEditor->selectTimerByID(mItemID);
+ mpItem = mpTreeWidgetTimers->currentItem();
+
+ mpTimersMainArea->timeEdit_timer_seconds->blockSignals(true);
+ mpTimersMainArea->timeEdit_timer_seconds->setTime(QTime(0, 0, mTimerSeconds, 0));
+ mpTimersMainArea->timeEdit_timer_seconds->blockSignals(false);
+ setText(QObject::tr("Edit timer seconds"));
+}
+
+TimerMilliSecondsTextEditedCommand::TimerMilliSecondsTextEditedCommand(Host* pHost, dlgTimersMainArea* timersMainArea, QUndoCommand* parent) : QUndoCommand(parent), mpHost(pHost)
+{
+ mpTimersMainArea = timersMainArea;
+}
+
+void TimerMilliSecondsTextEditedCommand::undo()
+{
+ if (!mpItem) {
+ return;
+ }
+
+ mpEditor->selectTimerByID(mItemID);
+ mpItem = mpTreeWidgetTimers->currentItem();
+
+ mpTimersMainArea->timeEdit_timer_msecs->blockSignals(true);
+ mpTimersMainArea->timeEdit_timer_msecs->setTime(QTime(0, 0, 0, mPrevTimerMsecs));
+ mpTimersMainArea->timeEdit_timer_msecs->blockSignals(false);
+}
+
+void TimerMilliSecondsTextEditedCommand::redo()
+{
+ if (!mpItem) {
+ return;
+ }
+ mItemID = mpItem->data(0, Qt::UserRole).toInt();
+ mpEditor->selectTimerByID(mItemID);
+ mpItem = mpTreeWidgetTimers->currentItem();
+
+ mpTimersMainArea->timeEdit_timer_msecs->blockSignals(true);
+ mpTimersMainArea->timeEdit_timer_msecs->setTime(QTime(0, 0, 0, mTimerMsecs));
+ mpTimersMainArea->timeEdit_timer_msecs->blockSignals(false);
+ setText(QObject::tr("Edit timer msecs"));
+}
+
+AddScriptCommand::AddScriptCommand(Host* pHost, TTreeWidget* treeWidget_scripts, bool isFolder, QUndoCommand* parent) : QUndoCommand(parent), mpHost(pHost)
+{
+ mpTreeWidgetScripts = treeWidget_scripts;
+ mIsFolder = isFolder;
+}
+
+void AddScriptCommand::undo()
+{
+ if (!mpItem) {
+ return;
+ }
+
+ mpEditor->selectScriptByID(mItemID);
+ mpItem = mpTreeWidgetScripts->currentItem();
+ const int id = mpItem->data(0, Qt::UserRole).toInt();
+ TScript* pT = mpHost->getScriptUnit()->getScript(id);
+ mpItemScript = pT;
+
+ mpParent = mpItem->parent();
+
+ if (mpParent) {
+ mpParent->removeChild(mpItem);
+ const int parentId = mpParent->data(0, Qt::UserRole).toInt();
+ TScript* pParent = mpHost->getScriptUnit()->getScript(parentId);
+ if (pParent) {
+ pParent->popChild(mpItemScript);
+ }
+ } else {
+ qDebug() << "parent is null ";
+ }
+}
+
+void AddScriptCommand::redo()
+{
+ if (!mpEditor) {
+ return;
+ }
+ if (!mpItem) {
+ mpEditor->addScript(mIsFolder);
+ mpItem = mpTreeWidgetScripts->currentItem();
+ mCurrentIndex = mpTreeWidgetScripts->currentIndex();
+ mSiblingRow = mCurrentIndex.row();
+ mpParent = mpItem->parent();
+ mItemID = mpItem->data(0, Qt::UserRole).toInt();
+ } else {
+ const int parentId = mpParent->data(0, Qt::UserRole).toInt();
+ TScript* pParent = mpHost->getScriptUnit()->getScript(parentId);
+
+ if (mIsFolder) {
+ mpParent->addChild(mpItem);
+ } else {
+ mpParent->insertChild(mSiblingRow, mpItem);
+ }
+ if (pParent) {
+ TScript* pT = mpHost->getScriptUnit()->getScript(mItemID);
+ pParent->addChild(mpItemScript);
+ }
+
+ mpEditor->selectScriptByID(mItemID);
+ }
+
+ setText(QObject::tr("Add script"));
+}
+
+PasteScriptCommand::PasteScriptCommand(Host* pHost, TTreeWidget* treeWidgetScripts, QUndoCommand* parent) : QUndoCommand(parent), mpHost(pHost)
+{
+ mpTreeWidgetScripts = treeWidgetScripts;
+}
+
+void PasteScriptCommand::undo()
+{
+ if (!mpEditor) {
+ return;
+ }
+
+ mpEditor->selectScriptByID(mImportedItemID);
+ mpItem = mpTreeWidgetScripts->currentItem();
+ TScript* pT = mpHost->getScriptUnit()->getScript(mImportedItemID);
+ mpItemScript = pT;
+ mpParent = mpItem->parent();
+ if (mpParent) {
+ mpParent->removeChild(mpItem);
+ const int parentId = mpParent->data(0, Qt::UserRole).toInt();
+ TScript* pParent = mpHost->getScriptUnit()->getScript(parentId);
+ if (pParent) {
+ pParent->popChild(mpItemScript);
+ }
+ } else {
+ qDebug() << "parent is null ";
+ }
+}
+
+void PasteScriptCommand::redo()
+{
+ if (!mpItem) {
+ auto parent = mpTreeWidgetScripts->currentIndex().parent();
+ auto parentRow = parent.row();
+
+ mSiblingRow = mpTreeWidgetScripts->currentIndex().row() + 1;
+ mpItemScript = mpHost->getScriptUnit()->getScript(mImportedItemID);
+ const int parentId = mpParent->data(0, Qt::UserRole).toInt();
+ mpHost->getScriptUnit()->reParentScript(mImportedItemID, 0, parentId, parentRow, mSiblingRow);
+
+ mpEditor->selectScriptByID(mImportedItemID);
+ mpItem = mpTreeWidgetScripts->currentItem();
+ mpParent = mpItem->parent();
+ } else {
+ const int parentId = mpParent->data(0, Qt::UserRole).toInt();
+ TScript* pParent = mpHost->getScriptUnit()->getScript(parentId);
+ const int id = mpItem->data(0, Qt::UserRole).toInt();
+ TScript* pT = mpHost->getScriptUnit()->getScript(id);
+ mpItemScript = pT;
+
+ if (pParent) {
+ mpParent->insertChild(mSiblingRow, mpItem);
+ } else {
+ mpParent->addChild(mpItem);
+ }
+ if (pParent) {
+ pParent->addChild(mpItemScript);
+ }
+
+ mpEditor->selectScriptByID(id);
+ }
+
+ setText(QObject::tr("Paste script"));
+}
+
+DeleteScriptCommand::DeleteScriptCommand(Host* pHost, QTreeWidgetItem* pItem, TTreeWidget* treeWidgetScripts, QUndoCommand* parent) : QUndoCommand(parent), mpHost(pHost)
+{
+ mpItem = pItem;
+ mpTreeWidgetScripts = treeWidgetScripts;
+}
+
+void DeleteScriptCommand::undo()
+{
+ if (!mpItem) {
+ return;
+ }
+ mpEditor->selectScriptByID(mParentID);
+ auto mParent = mpTreeWidgetScripts->currentItem();
+ mpParent = mParent;
+ if (mpParent) {
+ QList<QTreeWidgetItem*> childList;
+ mpTreeWidgetScripts->getAllChildren(mpItem, childList);
+ if (mpItemScriptList.size() != childList.size()) {
+ return;
+ }
+ for (int i = 0; i < childList.size(); ++i) {
+ mpItemScriptList[i]->setTemporary(false);
+ mpItemScriptList[i]->registerScript();
+ childList[i]->setData(0, Qt::UserRole, mpItemScriptList[i]->getID());
+ }
+ mpParent->insertChild(mSiblingRow, mpItem);
+
+ const int itemId = mpItem->data(0, Qt::UserRole).toInt();
+ const int parentId = mpParent->data(0, Qt::UserRole).toInt();
+ mpHost->getScriptUnit()->reParentScript(itemId, -1, parentId, mParentRow, mSiblingRow);
+ mpEditor->selectScriptByID(itemId);
+ mpItem = mpTreeWidgetScripts->currentItem();
+ } else {
+ qDebug() << "parent is null ";
+ }
+}
+
+void DeleteScriptCommand::redo()
+{
+ if (!mpEditor || !mpHost) {
+ return;
+ }
+ if (mpItem) {
+ auto parent = mCurrentIndex.parent();
+ auto parentRow = parent.row();
+ auto parentId = parent.data(Qt::UserRole).toInt();
+ mParentID = parentId;
+ const int siblingRow = mCurrentIndex.row();
+ mParentRow = parentRow;
+ mSiblingRow = siblingRow;
+ int itemId = mpItem->data(0, Qt::UserRole).toInt();
+ mpParent = mpItem->parent();
+ mpEditor->selectScriptByID(itemId);
+ mpItemScriptList.clear();
+ recurseScripts(mpItem, mpHost);
+ mpEditor->delete_script();
+ }
+ setText(QObject::tr("Delete script"));
+}
+
+void DeleteScriptCommand::recurseScripts(QTreeWidgetItem* mpItem, QPointer<Host> mpHost)
+{
+ TScript* ptr = nullptr;
+ QList<QTreeWidgetItem*> childList;
+ mpTreeWidgetScripts->getAllChildren(mpItem, childList);
+ for (int i = 0; i < childList.size(); ++i) {
+ const int pID = childList[i]->data(0, Qt::UserRole).toInt();
+ TScript* pChild = mpHost->getScriptUnit()->getScript(pID);
+ if (!pChild) {
+ continue;
+ }
+ if (pChild->getParent() && mpItemScriptList.size() > 0) {
+ for (int j = 0; j < mpItemScriptList.size(); j++) {
+ if (mpItemScriptList[j]->mID == pChild->getParent()->mID) {
+ ptr = new TScript(mpItemScriptList[j], mpHost);
+ break;
+ }
+ }
+ } else {
+ ptr = new TScript(pChild->getName(), mpHost);
+ }
+ ptr->setID(pChild->mID);
+ ptr->setIsFolder(pChild->isFolder());
+ ptr->setIsActive(pChild->isActive());
+ ptr->setName(pChild->getName());
+ ptr->setScript(pChild->getScript());
+ ptr->setEventHandlerList(pChild->getEventHandlerList());
+ ptr->setTemporary(true);
+ mpItemScriptList.append(ptr);
+ }
+}
+MoveScriptCommand::MoveScriptCommand(Host* pHost,
+ TTreeWidget* treeWidget_scripts,
+ int childID,
+ int oldParentID,
+ int newParentID,
+ int parentPosition,
+ int childPosition,
+ int prevParentPosition,
+ int prevChildPosition,
+ QUndoCommand* parent)
+: QUndoCommand(parent), mpHost(pHost)
+{
+ mChildID = childID;
+ mOldParentID = oldParentID;
+ mNewParentID = newParentID;
+ mParentPosition = parentPosition;
+ mChildPosition = childPosition;
+ mPrevParentPosition = prevParentPosition;
+ mPrevChildPosition = prevChildPosition;
+ mpTreeWidgetScripts = treeWidget_scripts;
+}
+
+void MoveScriptCommand::undo()
+{
+ if (!mpHost) {
+ return;
+ }
+
+ mpEditor->selectScriptByID(mItemID);
+ mpItem = mpTreeWidgetScripts->currentItem();
+
+ mpEditor->selectScriptByID(mOldParentID);
+ mpPrevParentItem = mpTreeWidgetScripts->currentItem();
+
+ mpEditor->selectScriptByID(mNewParentID);
+ mpParentItem = mpTreeWidgetScripts->currentItem();
+
+ mpHost->getScriptUnit()->reParentScript(mChildID, mNewParentID, mOldParentID, mPrevParentPosition, mPrevChildPosition);
+ mpParentItem->removeChild(mpItem);
+
+ mpPrevParentItem->insertChild(mPrevChildPosition, mpItem);
+
+ mpTreeWidgetScripts->setCurrentItem(mpItem);
+}
+
+void MoveScriptCommand::redo()
+{
+ if (!mpEditor) {
+ return;
+ }
+ mItemID = mpItem->data(0, Qt::UserRole).toInt();
+ mpHost->getScriptUnit()->reParentScript(mChildID, mOldParentID, mNewParentID, mParentPosition, mChildPosition);
+ if (mpPrevParentItem) {
+ mpPrevParentItem->removeChild(mpItem);
+ }
+ if (mpParentItem) {
+ const int count = mpParentItem->childCount();
+ mpParentItem->insertChild(count <= 0 ? 0 : count, mpItem);
+ }
+
+ setText(QObject::tr("Move script"));
+}
+
+ActiveToggleScriptEditedCommand::ActiveToggleScriptEditedCommand(Host* pHost, dlgScriptsMainArea* scriptsMainArea, QUndoCommand* parent) : QUndoCommand(parent), mpHost(pHost)
+{
+ mpScriptsMainArea = scriptsMainArea;
+}
+
+void ActiveToggleScriptEditedCommand::undo()
+{
+ if (!mpItem) {
+ return;
+ }
+
+ mpEditor->selectScriptByID(mItemID);
+ mpItem = mpTreeWidgetScripts->currentItem();
+ QIcon icon;
+ QString itemDescription;
+
+ mpItemScript->setIsActive(mPrevScriptToggle);
+
+ if (mpItemScript->isFolder()) {
+ if (mpItemScript->isActive()) {
+ icon.addPixmap(QPixmap(qsl(":/icons/folder-violet.png")), QIcon::Normal, QIcon::Off);
+ itemDescription = descActiveFolder;
+ } else {
+ icon.addPixmap(QPixmap(qsl(":/icons/folder-violet-locked.png")), QIcon::Normal, QIcon::Off);
+ itemDescription = descInactiveFolder;
+ }
+ } else {
+ if (mpItemScript->isActive()) {
+ itemDescription = descActive;
+ if (mpItemScript->ancestorsActive()) {
+ icon.addPixmap(QPixmap(qsl(":/icons/tag_checkbox_checked.png")), QIcon::Normal, QIcon::Off);
+ } else {
+ icon.addPixmap(QPixmap(qsl(":/icons/tag_checkbox_checked_grey.png")), QIcon::Normal, QIcon::Off);
+ itemDescription = descInactiveParent.arg(itemDescription);
+ }
+ } else {
+ icon.addPixmap(QPixmap(qsl(":/icons/tag_checkbox.png")), QIcon::Normal, QIcon::Off);
+ itemDescription = descInactive;
+ }
+ }
+
+ if (!mpItemScript->state()) {
+ mpItemScript->setIsActive(false);
+ mpEditor->showError(mpEditor->tr(R"(<p><b>Unable to activate "<tt>%1</tt>"; %2.</b></p>
+ <p><i>You will need to reactivate this after the problem has been corrected.</i></p>)")
+ .arg(mpItemScript->getName().toHtmlEscaped(), mpItemScript->getError()));
+ icon.addPixmap(QPixmap(qsl(":/icons/tools-report-bug.png")), QIcon::Normal, QIcon::Off);
+ itemDescription = descError;
+ }
+ mpItem->setIcon(0, icon);
+ mpItem->setText(0, mpItemScript->getName());
+ mpItem->setData(0, Qt::AccessibleDescriptionRole, itemDescription);
+
+ if (mpItem->childCount() > 0) {
+ mpEditor->children_icon_alias(mpItem);
+ }
+ // mpEditor->slot_saveSelectedItem(mpItem);
+}
+
+void ActiveToggleScriptEditedCommand::redo()
+{
+ if (!mpItem) {
+ return;
+ }
+ mItemID = mpItem->data(0, Qt::UserRole).toInt();
+ mpEditor->selectScriptByID(mItemID);
+ mpItem = mpTreeWidgetScripts->currentItem();
+ QIcon icon;
+ QString itemDescription;
+ // bool prevTriggerToggle = mpItemTrigger->isActive();
+ mpItemScript->setIsActive(mScriptToggle);
+
+ if (mpItemScript->isFolder()) {
+ if (mpItemScript->isActive()) {
+ icon.addPixmap(QPixmap(qsl(":/icons/folder-violet.png")), QIcon::Normal, QIcon::Off);
+ itemDescription = descActiveFolder;
+ } else {
+ icon.addPixmap(QPixmap(qsl(":/icons/folder-violet-locked.png")), QIcon::Normal, QIcon::Off);
+ itemDescription = descInactiveFolder;
+ }
+ } else {
+ if (mpItemScript->isActive()) {
+ itemDescription = descActive;
+ if (mpItemScript->ancestorsActive()) {
+ icon.addPixmap(QPixmap(qsl(":/icons/tag_checkbox_checked.png")), QIcon::Normal, QIcon::Off);
+ } else {
+ icon.addPixmap(QPixmap(qsl(":/icons/tag_checkbox_checked_grey.png")), QIcon::Normal, QIcon::Off);
+ itemDescription = descInactiveParent.arg(itemDescription);
+ }
+ } else {
+ icon.addPixmap(QPixmap(qsl(":/icons/tag_checkbox.png")), QIcon::Normal, QIcon::Off);
+ itemDescription = descInactive;
+ }
+ }
+
+ if (!mpItemScript->state()) {
+ mpItemScript->setIsActive(false);
+ mpEditor->showError(mpEditor->tr(R"(<p><b>Unable to activate "<tt>%1</tt>"; %2.</b></p>
+ <p><i>You will need to reactivate this after the problem has been corrected.</i></p>)")
+ .arg(mpItemScript->getName().toHtmlEscaped(), mpItemScript->getError()));
+ icon.addPixmap(QPixmap(qsl(":/icons/tools-report-bug.png")), QIcon::Normal, QIcon::Off);
+ itemDescription = descError;
+ }
+ mpItem->setIcon(0, icon);
+ mpItem->setText(0, mpItemScript->getName());
+ mpItem->setData(0, Qt::AccessibleDescriptionRole, itemDescription);
+
+ if (mpItem->childCount() > 0) {
+ mpEditor->children_icon_alias(mpItem);
+ }
+ // mpEditor->slot_saveSelectedItem(mpItem);
+ setText(QObject::tr("Edit script toggle"));
+}
+
+ScriptNameTextEditedCommand::ScriptNameTextEditedCommand(Host* pHost, dlgScriptsMainArea* scriptsMainArea, QUndoCommand* parent) : QUndoCommand(parent), mpHost(pHost)
+{
+ mpScriptsMainArea = scriptsMainArea;
+}
+
+void ScriptNameTextEditedCommand::undo()
+{
+ if (!mpItem) {
+ return;
+ }
+
+ mpEditor->selectScriptByID(mItemID);
+ mpItem = mpTreeWidgetScripts->currentItem();
+
+ mpScriptsMainArea->lineEdit_script_name->setText(mPrevScriptName);
+ mpEditor->slot_saveSelectedItem(mpItem);
+}
+
+void ScriptNameTextEditedCommand::redo()
+{
+ if (!mpItem) {
+ return;
+ }
+ mItemID = mpItem->data(0, Qt::UserRole).toInt();
+ mpEditor->selectScriptByID(mItemID);
+ mpItem = mpTreeWidgetScripts->currentItem();
+
+ mpScriptsMainArea->lineEdit_script_name->setText(mScriptName);
+ mpEditor->slot_saveSelectedItem(mpItem);
+ setText(QObject::tr("Edit script name"));
+}
+
+ScriptAddHandlerCommand::ScriptAddHandlerCommand(Host* pHost, dlgScriptsMainArea* scriptsMainArea, QUndoCommand* parent) : QUndoCommand(parent), mpHost(pHost)
+{
+ mpScriptsMainArea = scriptsMainArea;
+}
+
+void ScriptAddHandlerCommand::undo()
+{
+ if (!mpItem) {
+ return;
+ }
+ mpEditor->selectScriptByID(mItemID);
+ mpItem = mpTreeWidgetScripts->currentItem();
+ auto pItem = new QListWidgetItem;
+ pItem->setText(mPrevScriptEventhandler);
+ mpScriptsMainArea->listWidget_script_registered_event_handlers->takeItem(mRow);
+ mpEditor->slot_saveSelectedItem(mpItem);
+}
+
+void ScriptAddHandlerCommand::redo()
+{
+ if (!mpItem) {
+ return;
+ }
+ mItemID = mpItem->data(0, Qt::UserRole).toInt();
+ mpEditor->selectScriptByID(mItemID);
+ mpItem = mpTreeWidgetScripts->currentItem();
+ auto pItem = new QListWidgetItem;
+ pItem->setText(mScriptEventhandler);
+ mpScriptsMainArea->listWidget_script_registered_event_handlers->addItem(pItem);
+ mpWidgetItem = pItem;
+ mRow = mpScriptsMainArea->listWidget_script_registered_event_handlers->row(pItem);
+ mpScriptsMainArea->listWidget_script_registered_event_handlers->setCurrentRow(mRow);
+ mpEditor->slot_saveSelectedItem(mpItem);
+ setText(QObject::tr("Add script handler"));
+}
+
+ScriptRemoveHandlerCommand::ScriptRemoveHandlerCommand(Host* pHost, dlgScriptsMainArea* scriptsMainArea, QUndoCommand* parent) : QUndoCommand(parent), mpHost(pHost)
+{
+ mpScriptsMainArea = scriptsMainArea;
+}
+
+void ScriptRemoveHandlerCommand::undo()
+{
+ if (!mpItem) {
+ return;
+ }
+
+ mpEditor->selectScriptByID(mItemID);
+ mpItem = mpTreeWidgetScripts->currentItem();
+ mpScriptsMainArea->listWidget_script_registered_event_handlers->addItem(mpWidgetItem);
+ mpEditor->slot_saveSelectedItem(mpItem);
+}
+
+void ScriptRemoveHandlerCommand::redo()
+{
+ if (!mpItem) {
+ return;
+ }
+
+ mItemID = mpItem->data(0, Qt::UserRole).toInt();
+ mpEditor->selectScriptByID(mItemID);
+ mpItem = mpTreeWidgetScripts->currentItem();
+
+ mpWidgetItem = mpScriptsMainArea->listWidget_script_registered_event_handlers->takeItem(mRow);
+ mpEditor->slot_saveSelectedItem(mpItem);
+ setText(QObject::tr("Remove script handler"));
+}
+
+AddKeyCommand::AddKeyCommand(Host* pHost, TTreeWidget* treeWidget_keys, bool isFolder, QUndoCommand* parent) : QUndoCommand(parent), mpHost(pHost)
+{
+ mpTreeWidgetKeys = treeWidget_keys;
+ mIsFolder = isFolder;
+}
+
+void AddKeyCommand::undo()
+{
+ if (!mpItem) {
+ return;
+ }
+
+ mpEditor->selectKeyByID(mItemID);
+ mpItem = mpTreeWidgetKeys->currentItem();
+
+ mpParent = mpItem->parent();
+
+ if (mpParent) {
+ mpParent->removeChild(mpItem);
+ const int parentId = mpParent->data(0, Qt::UserRole).toInt();
+ TKey* pParent = mpHost->getKeyUnit()->getKey(parentId);
+ if (pParent) {
+ TKey* pT = mpHost->getKeyUnit()->getKey(mItemID);
+ pParent->popChild(pT);
+ }
+ } else {
+ qDebug() << "parent is null ";
+ }
+}
+
+void AddKeyCommand::redo()
+{
+ if (!mpEditor) {
+ return;
+ }
+ if (!mpItem) {
+ mpEditor->addKey(mIsFolder);
+ mpItem = mpTreeWidgetKeys->currentItem();
+ mCurrentIndex = mpTreeWidgetKeys->currentIndex();
+ mSiblingRow = mCurrentIndex.row();
+ mpParent = mpItem->parent();
+ mItemID = mpItem->data(0, Qt::UserRole).toInt();
+ } else {
+ const int parentId = mpParent->data(0, Qt::UserRole).toInt();
+ TKey* pParent = mpHost->getKeyUnit()->getKey(parentId);
+ if (mIsFolder) {
+ mpParent->addChild(mpItem);
+ } else {
+ mpParent->insertChild(mSiblingRow, mpItem);
+ }
+ if (pParent) {
+ TKey* pT = mpHost->getKeyUnit()->getKey(mItemID);
+ pParent->addChild(pT);
+ }
+ }
+
+ setText(QObject::tr("Add key"));
+}
+
+PasteKeyCommand::PasteKeyCommand(Host* pHost, TTreeWidget* treeWidgetKeys, QUndoCommand* parent) : QUndoCommand(parent), mpHost(pHost)
+{
+ mpTreeWidgetKeys = treeWidgetKeys;
+}
+
+void PasteKeyCommand::undo()
+{
+ if (!mpEditor) {
+ return;
+ }
+
+ mpEditor->selectKeyByID(mImportedItemID);
+ mpItem = mpTreeWidgetKeys->currentItem();
+ TKey* pT = mpHost->getKeyUnit()->getKey(mImportedItemID);
+ mpItemKey = pT;
+ mpParent = mpItem->parent();
+ if (mpParent) {
+ mpParent->removeChild(mpItem);
+ const int parentId = mpParent->data(0, Qt::UserRole).toInt();
+ TKey* pParent = mpHost->getKeyUnit()->getKey(parentId);
+ if (pParent) {
+ pParent->popChild(mpItemKey);
+ }
+ } else {
+ qDebug() << "parent is null ";
+ }
+}
+
+void PasteKeyCommand::redo()
+{
+ if (!mpItem) {
+ auto parent = mpTreeWidgetKeys->currentIndex().parent();
+ auto parentRow = parent.row();
+
+ mSiblingRow = mpTreeWidgetKeys->currentIndex().row() + 1;
+ mpItemKey = mpHost->getKeyUnit()->getKey(mImportedItemID);
+ const int parentId = mpParent->data(0, Qt::UserRole).toInt();
+ mpHost->getKeyUnit()->reParentKey(mImportedItemID, 0, parentId, parentRow, mSiblingRow);
+
+ mpEditor->selectKeyByID(mImportedItemID);
+ mpItem = mpTreeWidgetKeys->currentItem();
+ mpParent = mpItem->parent();
+ } else {
+ const int parentId = mpParent->data(0, Qt::UserRole).toInt();
+ TKey* pParent = mpHost->getKeyUnit()->getKey(parentId);
+ const int id = mpItem->data(0, Qt::UserRole).toInt();
+ TKey* pT = mpHost->getKeyUnit()->getKey(id);
+ mpItemKey = pT;
+
+ if (pParent) {
+ mpParent->insertChild(mSiblingRow, mpItem);
+ } else {
+ mpParent->addChild(mpItem);
+ }
+ if (pParent) {
+ pParent->addChild(mpItemKey);
+ }
+
+ mpEditor->selectKeyByID(id);
+ }
+
+ setText(QObject::tr("Paste key"));
+}
+
+DeleteKeyCommand::DeleteKeyCommand(Host* pHost, QTreeWidgetItem* pItem, TTreeWidget* treeWidget_keys, QUndoCommand* parent) : QUndoCommand(parent), mpHost(pHost)
+{
+ mpItem = pItem;
+ mpTreeWidgetKeys = treeWidget_keys;
+}
+
+void DeleteKeyCommand::undo()
+{
+ if (!mpItem) {
+ return;
+ }
+ mpEditor->selectKeyByID(mParentID);
+ auto mParent = mpTreeWidgetKeys->currentItem();
+ mpParent = mParent;
+ if (mpParent) {
+ QList<QTreeWidgetItem*> childList;
+ mpTreeWidgetKeys->getAllChildren(mpItem, childList);
+ if (mpItemKeyList.size() != childList.size()) {
+ return;
+ }
+ for (int i = 0; i < childList.size(); ++i) {
+ mpItemKeyList[i]->setTemporary(false);
+ mpHost->getKeyUnit()->registerKey(mpItemKeyList[i]);
+ childList[i]->setData(0, Qt::UserRole, mpItemKeyList[i]->getID());
+ }
+ mpParent->insertChild(mSiblingRow, mpItem);
+
+ const int itemId = mpItem->data(0, Qt::UserRole).toInt();
+ const int parentId = mpParent->data(0, Qt::UserRole).toInt();
+ mpHost->getKeyUnit()->reParentKey(itemId, -1, parentId, mParentRow, mSiblingRow);
+ mpEditor->selectKeyByID(itemId);
+ mpItem = mpTreeWidgetKeys->currentItem();
+ } else {
+ qDebug() << "parent is null ";
+ }
+}
+
+void DeleteKeyCommand::redo()
+{
+ if (!mpEditor || !mpHost) {
+ return;
+ }
+ if (mpItem) {
+ auto parent = mCurrentIndex.parent();
+ auto parentRow = parent.row();
+ auto parentId = parent.data(Qt::UserRole).toInt();
+ mParentID = parentId;
+ const int siblingRow = mCurrentIndex.row();
+ mParentRow = parentRow;
+ mSiblingRow = siblingRow;
+ int itemId = mpItem->data(0, Qt::UserRole).toInt();
+ mpParent = mpItem->parent();
+ mpEditor->selectKeyByID(itemId);
+ mpItemKeyList.clear();
+ recurseKeys(mpItem, mpHost);
+ mpEditor->delete_key();
+ }
+ setText(QObject::tr("Delete key"));
+}
+
+void DeleteKeyCommand::recurseKeys(QTreeWidgetItem* mpItem, QPointer<Host> mpHost)
+{
+ TKey* ptr = nullptr;
+ QList<QTreeWidgetItem*> childList;
+ mpTreeWidgetKeys->getAllChildren(mpItem, childList);
+ for (int i = 0; i < childList.size(); ++i) {
+ const int pID = childList[i]->data(0, Qt::UserRole).toInt();
+ TKey* pChild = mpHost->getKeyUnit()->getKey(pID);
+ if (pChild->getParent() && mpItemKeyList.size() > 0) {
+ for (int j = 0; j < mpItemKeyList.size(); j++) {
+ if (mpItemKeyList[j]->mID == pChild->getParent()->mID) {
+ ptr = new TKey(mpItemKeyList[j], mpHost);
+ break;
+ }
+ }
+ } else {
+ ptr = new TKey(pChild->getName(), mpHost);
+ }
+ ptr->setID(pChild->mID);
+ ptr->setIsFolder(pChild->isFolder());
+ ptr->setIsActive(pChild->isActive());
+ ptr->setName(pChild->getName());
+ ptr->setCommand(pChild->getCommand());
+ ptr->setKeyCode(pChild->getKeyCode());
+ ptr->setKeyModifiers(pChild->getKeyModifiers());
+ ptr->setScript(pChild->getScript());
+ ptr->setTemporary(true);
+ mpItemKeyList.append(ptr);
+ }
+}
+
+MoveKeyCommand::MoveKeyCommand(Host* pHost,
+ TTreeWidget* treeWidget_keys,
+ int childID,
+ int oldParentID,
+ int newParentID,
+ int parentPosition,
+ int childPosition,
+ int prevParentPosition,
+ int prevChildPosition,
+ QUndoCommand* parent)
+: QUndoCommand(parent), mpHost(pHost)
+{
+ mChildID = childID;
+ mOldParentID = oldParentID;
+ mNewParentID = newParentID;
+ mParentPosition = parentPosition;
+ mChildPosition = childPosition;
+ mPrevParentPosition = prevParentPosition;
+ mPrevChildPosition = prevChildPosition;
+ mpTreeWidgetKeys = treeWidget_keys;
+}
+
+void MoveKeyCommand::undo()
+{
+ if (!mpHost) {
+ return;
+ }
+
+ mpEditor->selectKeyByID(mItemID);
+ mpItem = mpTreeWidgetKeys->currentItem();
+
+ mpEditor->selectKeyByID(mOldParentID);
+ mpPrevParentItem = mpTreeWidgetKeys->currentItem();
+
+ mpEditor->selectKeyByID(mNewParentID);
+ mpParentItem = mpTreeWidgetKeys->currentItem();
+
+ mpHost->getKeyUnit()->reParentKey(mChildID, mNewParentID, mOldParentID, mPrevParentPosition, mPrevChildPosition);
+ mpParentItem->removeChild(mpItem);
+
+ mpPrevParentItem->insertChild(mPrevChildPosition, mpItem);
+
+ mpTreeWidgetKeys->setCurrentItem(mpItem);
+}
+
+void MoveKeyCommand::redo()
+{
+ if (!mpEditor) {
+ return;
+ }
+ mItemID = mpItem->data(0, Qt::UserRole).toInt();
+ mpHost->getKeyUnit()->reParentKey(mChildID, mOldParentID, mNewParentID, mParentPosition, mChildPosition);
+ if (mpPrevParentItem) {
+ mpPrevParentItem->removeChild(mpItem);
+ }
+ if (mpParentItem) {
+ const int count = mpParentItem->childCount();
+ mpParentItem->insertChild(count <= 0 ? 0 : count, mpItem);
+ }
+
+ setText(QObject::tr("Move key"));
+}
+
+ActiveToggleKeyEditedCommand::ActiveToggleKeyEditedCommand(Host* pHost, dlgKeysMainArea* keysMainArea, QUndoCommand* parent) : QUndoCommand(parent), mpHost(pHost)
+{
+ mpKeysMainArea = keysMainArea;
+}
+
+void ActiveToggleKeyEditedCommand::undo()
+{
+ if (!mpItem) {
+ return;
+ }
+
+ mpEditor->selectKeyByID(mItemID);
+ mpItem = mpTreeWidgetKeys->currentItem();
+ QIcon icon;
+ QString itemDescription;
+
+ mpItemKey->setIsActive(mPrevKeyToggle);
+
+ if (mpItemKey->isFolder()) {
+ if (mpItemKey->isActive()) {
+ icon.addPixmap(QPixmap(qsl(":/icons/folder-violet.png")), QIcon::Normal, QIcon::Off);
+ itemDescription = descActiveFolder;
+ } else {
+ icon.addPixmap(QPixmap(qsl(":/icons/folder-violet-locked.png")), QIcon::Normal, QIcon::Off);
+ itemDescription = descInactiveFolder;
+ }
+ } else {
+ if (mpItemKey->isActive()) {
+ itemDescription = descActive;
+ if (mpItemKey->ancestorsActive()) {
+ icon.addPixmap(QPixmap(qsl(":/icons/tag_checkbox_checked.png")), QIcon::Normal, QIcon::Off);
+ } else {
+ icon.addPixmap(QPixmap(qsl(":/icons/tag_checkbox_checked_grey.png")), QIcon::Normal, QIcon::Off);
+ itemDescription = descInactiveParent.arg(itemDescription);
+ }
+ } else {
+ icon.addPixmap(QPixmap(qsl(":/icons/tag_checkbox.png")), QIcon::Normal, QIcon::Off);
+ itemDescription = descInactive;
+ }
+ }
+
+ if (!mpItemKey->state()) {
+ mpItemKey->setIsActive(false);
+ mpEditor->showError(mpEditor->tr(R"(<p><b>Unable to activate "<tt>%1</tt>"; %2.</b></p>
+ <p><i>You will need to reactivate this after the problem has been corrected.</i></p>)")
+ .arg(mpItemKey->getName().toHtmlEscaped(), mpItemKey->getError()));
+ icon.addPixmap(QPixmap(qsl(":/icons/tools-report-bug.png")), QIcon::Normal, QIcon::Off);
+ itemDescription = descError;
+ }
+ mpItem->setIcon(0, icon);
+ mpItem->setText(0, mpItemKey->getName());
+ mpItem->setData(0, Qt::AccessibleDescriptionRole, itemDescription);
+
+ if (mpItem->childCount() > 0) {
+ mpEditor->children_icon_alias(mpItem);
+ }
+ // mpEditor->slot_saveSelectedItem(mpItem);
+}
+
+void ActiveToggleKeyEditedCommand::redo()
+{
+ if (!mpItem) {
+ return;
+ }
+ mItemID = mpItem->data(0, Qt::UserRole).toInt();
+ mpEditor->selectKeyByID(mItemID);
+ mpItem = mpTreeWidgetKeys->currentItem();
+ QIcon icon;
+ QString itemDescription;
+ // bool prevTriggerToggle = mpItemTrigger->isActive();
+ mpItemKey->setIsActive(mKeyToggle);
+
+ if (mpItemKey->isFolder()) {
+ if (mpItemKey->isActive()) {
+ icon.addPixmap(QPixmap(qsl(":/icons/folder-violet.png")), QIcon::Normal, QIcon::Off);
+ itemDescription = descActiveFolder;
+ } else {
+ icon.addPixmap(QPixmap(qsl(":/icons/folder-violet-locked.png")), QIcon::Normal, QIcon::Off);
+ itemDescription = descInactiveFolder;
+ }
+ } else {
+ if (mpItemKey->isActive()) {
+ itemDescription = descActive;
+ if (mpItemKey->ancestorsActive()) {
+ icon.addPixmap(QPixmap(qsl(":/icons/tag_checkbox_checked.png")), QIcon::Normal, QIcon::Off);
+ } else {
+ icon.addPixmap(QPixmap(qsl(":/icons/tag_checkbox_checked_grey.png")), QIcon::Normal, QIcon::Off);
+ itemDescription = descInactiveParent.arg(itemDescription);
+ }
+ } else {
+ icon.addPixmap(QPixmap(qsl(":/icons/tag_checkbox.png")), QIcon::Normal, QIcon::Off);
+ itemDescription = descInactive;
+ }
+ }
+
+ if (!mpItemKey->state()) {
+ mpItemKey->setIsActive(false);
+ mpEditor->showError(mpEditor->tr(R"(<p><b>Unable to activate "<tt>%1</tt>"; %2.</b></p>
+ <p><i>You will need to reactivate this after the problem has been corrected.</i></p>)")
+ .arg(mpItemKey->getName().toHtmlEscaped(), mpItemKey->getError()));
+ icon.addPixmap(QPixmap(qsl(":/icons/tools-report-bug.png")), QIcon::Normal, QIcon::Off);
+ itemDescription = descError;
+ }
+ mpItem->setIcon(0, icon);
+ mpItem->setText(0, mpItemKey->getName());
+ mpItem->setData(0, Qt::AccessibleDescriptionRole, itemDescription);
+
+ if (mpItem->childCount() > 0) {
+ mpEditor->children_icon_alias(mpItem);
+ }
+ // mpEditor->slot_saveSelectedItem(mpItem);
+ setText(QObject::tr("Edit key toggle"));
+}
+
+KeyNameTextEditedCommand::KeyNameTextEditedCommand(Host* pHost, dlgKeysMainArea* keysMainArea, QUndoCommand* parent) : QUndoCommand(parent), mpHost(pHost)
+{
+ mpKeysMainArea = keysMainArea;
+}
+
+void KeyNameTextEditedCommand::undo()
+{
+ if (!mpItem) {
+ return;
+ }
+
+ mpEditor->selectKeyByID(mItemID);
+ mpItem = mpTreeWidgetKeys->currentItem();
+
+ mpKeysMainArea->lineEdit_key_name->blockSignals(true);
+ mpKeysMainArea->lineEdit_key_name->setText(mPrevKeyName);
+ mpKeysMainArea->lineEdit_key_name->blockSignals(false);
+ mpEditor->slot_saveSelectedItem(mpItem);
+}
+
+void KeyNameTextEditedCommand::redo()
+{
+ if (!mpItem) {
+ return;
+ }
+ mItemID = mpItem->data(0, Qt::UserRole).toInt();
+ mpEditor->selectKeyByID(mItemID);
+ mpItem = mpTreeWidgetKeys->currentItem();
+
+ mpKeysMainArea->lineEdit_key_name->blockSignals(true);
+ mpKeysMainArea->lineEdit_key_name->setText(mKeyName);
+ mpKeysMainArea->lineEdit_key_name->blockSignals(false);
+ mpEditor->slot_saveSelectedItem(mpItem);
+ setText(QObject::tr("Edit key name"));
+}
+
+KeyCommandTextEditedCommand::KeyCommandTextEditedCommand(Host* pHost, dlgKeysMainArea* keysMainArea, QUndoCommand* parent) : QUndoCommand(parent), mpHost(pHost)
+{
+ mpKeysMainArea = keysMainArea;
+}
+
+void KeyCommandTextEditedCommand::undo()
+{
+ if (!mpItem) {
+ return;
+ }
+
+ mpEditor->selectKeyByID(mItemID);
+ mpItem = mpTreeWidgetKeys->currentItem();
+
+ mpKeysMainArea->lineEdit_key_command->blockSignals(true);
+ mpKeysMainArea->lineEdit_key_command->setText(mPrevKeyCommand);
+ mpKeysMainArea->lineEdit_key_command->blockSignals(false);
+ mpEditor->slot_saveSelectedItem(mpItem);
+}
+
+void KeyCommandTextEditedCommand::redo()
+{
+ if (!mpItem) {
+ return;
+ }
+ mItemID = mpItem->data(0, Qt::UserRole).toInt();
+ mpEditor->selectKeyByID(mItemID);
+ mpItem = mpTreeWidgetKeys->currentItem();
+
+ mpKeysMainArea->lineEdit_key_command->blockSignals(true);
+ mpKeysMainArea->lineEdit_key_command->setText(mKeyCommand);
+ mpKeysMainArea->lineEdit_key_command->blockSignals(false);
+ mpEditor->slot_saveSelectedItem(mpItem);
+ setText(QObject::tr("Edit key command"));
+}
+
+KeyGrabTextEditedCommand::KeyGrabTextEditedCommand(Host* pHost, dlgKeysMainArea* keysMainArea, QUndoCommand* parent) : QUndoCommand(parent), mpHost(pHost)
+{
+ mpKeysMainArea = keysMainArea;
+}
+
+void KeyGrabTextEditedCommand::undo()
+{
+ if (!mpItem) {
+ return;
+ }
+
+ mpEditor->selectKeyByID(mItemID);
+ mpItem = mpTreeWidgetKeys->currentItem();
+
+ const int triggerID = mpItem->data(0, Qt::UserRole).toInt();
+ TKey* pT = mpHost->getKeyUnit()->getKey(triggerID);
+ if (pT) {
+ pT->setKeyCode(mPrevKey);
+ pT->setKeyModifiers(mPrevModifier);
+ }
+ mpKeysMainArea->lineEdit_key_binding->blockSignals(true);
+ mpKeysMainArea->lineEdit_key_binding->setText(mPrevKeyName);
+ mpKeysMainArea->lineEdit_key_binding->blockSignals(false);
+ mpEditor->slot_saveSelectedItem(mpItem);
+}
+
+void KeyGrabTextEditedCommand::redo()
+{
+ if (!mpItem) {
+ return;
+ }
+
+ mItemID = mpItem->data(0, Qt::UserRole).toInt();
+ mpEditor->selectKeyByID(mItemID);
+ mpItem = mpTreeWidgetKeys->currentItem();
+
+ TKey* pT = mpHost->getKeyUnit()->getKey(mItemID);
+ if (pT) {
+ mPrevKey = pT->getKeyCode();
+ mPrevModifier = pT->getKeyModifiers();
+ pT->setKeyCode(mKey);
+ pT->setKeyModifiers(mModifier);
+ }
+ mpKeysMainArea->lineEdit_key_binding->blockSignals(true);
+ mpKeysMainArea->lineEdit_key_binding->setText(mKeyName);
+ mpKeysMainArea->lineEdit_key_binding->blockSignals(false);
+ mpEditor->slot_saveSelectedItem(mpItem);
+ setText(QObject::tr("Edit key modifer"));
+}
+
+AddActionCommand::AddActionCommand(Host* pHost, TTreeWidget* treeWidget_actions, bool isFolder, QUndoCommand* parent) : QUndoCommand(parent), mpHost(pHost)
+{
+ mpTreeWidgetActions = treeWidget_actions;
+ mIsFolder = isFolder;
+}
+
+void AddActionCommand::undo()
+{
+ if (!mpItem) {
+ return;
+ }
+
+ mpEditor->selectActionByID(mItemID);
+ mpItem = mpTreeWidgetActions->currentItem();
+ const int id = mpItem->data(0, Qt::UserRole).toInt();
+ TAction* pT = mpHost->getActionUnit()->getAction(id);
+ mpItemAction = pT;
+
+ mpParent = mpItem->parent();
+
+ if (mpParent) {
+ mpParent->removeChild(mpItem);
+ const int parentId = mpParent->data(0, Qt::UserRole).toInt();
+ TAction* pParent = mpHost->getActionUnit()->getAction(parentId);
+ if (pParent) {
+ pParent->popChild(mpItemAction);
+ }
+ } else {
+ qDebug() << "parent is null ";
+ }
+}
+
+void AddActionCommand::redo()
+{
+ if (!mpEditor) {
+ return;
+ }
+ if (!mpItem) {
+ mpEditor->addAction(mIsFolder);
+ mpItem = mpTreeWidgetActions->currentItem();
+ mCurrentIndex = mpTreeWidgetActions->currentIndex();
+ mSiblingRow = mCurrentIndex.row();
+ mpParent = mpItem->parent();
+ mItemID = mpItem->data(0, Qt::UserRole).toInt();
+ } else {
+ const int parentId = mpParent->data(0, Qt::UserRole).toInt();
+ TAction* pParent = mpHost->getActionUnit()->getAction(parentId);
+ if (mIsFolder) {
+ mpParent->addChild(mpItem);
+ } else {
+ mpParent->insertChild(mSiblingRow, mpItem);
+ }
+ if (pParent) {
+ TAction* pT = mpHost->getActionUnit()->getAction(mItemID);
+ pParent->addChild(pT);
+ }
+
+ mpEditor->selectActionByID(mItemID);
+ }
+
+ setText(QObject::tr("Add action"));
+}
+
+PasteActionCommand::PasteActionCommand(Host* pHost, TTreeWidget* treeWidgetActions, QUndoCommand* parent) : QUndoCommand(parent), mpHost(pHost)
+{
+ mpTreeWidgetActions = treeWidgetActions;
+}
+
+void PasteActionCommand::undo()
+{
+ if (!mpEditor) {
+ return;
+ }
+
+ mpEditor->selectActionByID(mImportedItemID);
+ mpItem = mpTreeWidgetActions->currentItem();
+ TAction* pT = mpHost->getActionUnit()->getAction(mImportedItemID);
+ mpItemAction = pT;
+ mpParent = mpItem->parent();
+ if (mpParent) {
+ mpParent->removeChild(mpItem);
+ const int parentId = mpParent->data(0, Qt::UserRole).toInt();
+ TAction* pParent = mpHost->getActionUnit()->getAction(parentId);
+ if (pParent) {
+ pParent->popChild(mpItemAction);
+ }
+ } else {
+ qDebug() << "parent is null ";
+ }
+}
+
+void PasteActionCommand::redo()
+{
+ if (!mpItem) {
+ auto parent = mpTreeWidgetActions->currentIndex().parent();
+ auto parentRow = parent.row();
+
+ mSiblingRow = mpTreeWidgetActions->currentIndex().row() + 1;
+ mpItemAction = mpHost->getActionUnit()->getAction(mImportedItemID);
+ const int parentId = mpParent->data(0, Qt::UserRole).toInt();
+ mpHost->getActionUnit()->reParentAction(mImportedItemID, 0, parentId, parentRow, mSiblingRow);
+
+ mpEditor->selectActionByID(mImportedItemID);
+ mpItem = mpTreeWidgetActions->currentItem();
+ mpParent = mpItem->parent();
+ } else {
+ const int parentId = mpParent->data(0, Qt::UserRole).toInt();
+ TAction* pParent = mpHost->getActionUnit()->getAction(parentId);
+ const int id = mpItem->data(0, Qt::UserRole).toInt();
+ TAction* pT = mpHost->getActionUnit()->getAction(id);
+ mpItemAction = pT;
+
+ if (pParent) {
+ mpParent->insertChild(mSiblingRow, mpItem);
+ } else {
+ mpParent->addChild(mpItem);
+ }
+ if (pParent) {
+ pParent->addChild(mpItemAction);
+ }
+
+ mpEditor->selectActionByID(id);
+ }
+
+ setText(QObject::tr("Paste action"));
+}
+
+DeleteActionCommand::DeleteActionCommand(Host* pHost, QTreeWidgetItem* pItem, TTreeWidget* treeWidget_actions, QUndoCommand* parent) : QUndoCommand(parent), mpHost(pHost)
+{
+ mpItem = pItem;
+ mpTreeWidgetActions = treeWidget_actions;
+}
+
+void DeleteActionCommand::undo()
+{
+ if (!mpItem) {
+ return;
+ }
+ mpEditor->selectActionByID(mParentID);
+ auto mParent = mpTreeWidgetActions->currentItem();
+ mpParent = mParent;
+ if (mpParent) {
+ QList<QTreeWidgetItem*> childList;
+ mpTreeWidgetActions->getAllChildren(mpItem, childList);
+ if (mpItemActionList.size() != childList.size()) {
+ return;
+ }
+ for (int i = 0; i < childList.size(); ++i) {
+ mpItemActionList[i]->setTemporary(false);
+ mpHost->getActionUnit()->registerAction(mpItemActionList[i]);
+ childList[i]->setData(0, Qt::UserRole, mpItemActionList[i]->getID());
+ }
+ mpParent->insertChild(mSiblingRow, mpItem);
+
+ const int itemId = mpItem->data(0, Qt::UserRole).toInt();
+ const int parentId = mpParent->data(0, Qt::UserRole).toInt();
+ mpHost->getActionUnit()->reParentAction(itemId, -1, parentId, mParentRow, mSiblingRow);
+ mpEditor->selectActionByID(itemId);
+ mpItem = mpTreeWidgetActions->currentItem();
+ } else {
+ qDebug() << "parent is null ";
+ }
+}
+
+void DeleteActionCommand::redo()
+{
+ if (!mpEditor || !mpHost) {
+ return;
+ }
+ if (mpItem) {
+ auto parent = mCurrentIndex.parent();
+ auto parentRow = parent.row();
+ auto parentId = parent.data(Qt::UserRole).toInt();
+ mParentID = parentId;
+ const int siblingRow = mCurrentIndex.row();
+ mParentRow = parentRow;
+ mSiblingRow = siblingRow;
+ int itemId = mpItem->data(0, Qt::UserRole).toInt();
+ mpParent = mpItem->parent();
+ mpEditor->selectActionByID(itemId);
+ mpItemActionList.clear();
+ recurseActions(mpItem, mpHost);
+ mpEditor->delete_action();
+ }
+ setText(QObject::tr("Delete action"));
+}
+
+void DeleteActionCommand::recurseActions(QTreeWidgetItem* mpItem, QPointer<Host> mpHost)
+{
+ TAction* ptr = nullptr;
+ QList<QTreeWidgetItem*> childList;
+ mpTreeWidgetActions->getAllChildren(mpItem, childList);
+ for (int i = 0; i < childList.size(); ++i) {
+ const int pID = childList[i]->data(0, Qt::UserRole).toInt();
+ TAction* pChild = mpHost->getActionUnit()->getAction(pID);
+ if (pChild->getParent() && mpItemActionList.size() > 0) {
+ for (int j = 0; j < mpItemActionList.size(); j++) {
+ if (mpItemActionList[j]->mID == pChild->getParent()->mID) {
+ ptr = new TAction(mpItemActionList[j], mpHost);
+ break;
+ }
+ }
+ } else {
+ ptr = new TAction(pChild->getName(), mpHost);
+ }
+ ptr->setID(pChild->mID);
+ ptr->setIsFolder(pChild->isFolder());
+ ptr->setIsActive(pChild->isActive());
+ ptr->setName(pChild->getName());
+ ptr->setButtonRotation(pChild->getButtonRotation());
+ ptr->setIsPushDownButton(pChild->isPushDownButton());
+ ptr->setCommandButtonUp(pChild->getCommandButtonUp());
+ ptr->setCommandButtonDown(pChild->getCommandButtonDown());
+ ptr->setScript(pChild->getScript());
+ ptr->setTemporary(true);
+ mpItemActionList.append(ptr);
+ }
+}
+
+MoveActionCommand::MoveActionCommand(Host* pHost,
+ TTreeWidget* treeWidget_actions,
+ int childID,
+ int oldParentID,
+ int newParentID,
+ int parentPosition,
+ int childPosition,
+ int prevParentPosition,
+ int prevChildPosition,
+ QUndoCommand* parent)
+: QUndoCommand(parent), mpHost(pHost)
+{
+ mChildID = childID;
+ mOldParentID = oldParentID;
+ mNewParentID = newParentID;
+ mParentPosition = parentPosition;
+ mChildPosition = childPosition;
+ mPrevParentPosition = prevParentPosition;
+ mPrevChildPosition = prevChildPosition;
+ mpTreeWidgetActions = treeWidget_actions;
+}
+
+void MoveActionCommand::undo()
+{
+ if (!mpHost) {
+ return;
+ }
+
+ mpEditor->selectActionByID(mItemID);
+ mpItem = mpTreeWidgetActions->currentItem();
+
+ mpEditor->selectActionByID(mOldParentID);
+ mpPrevParentItem = mpTreeWidgetActions->currentItem();
+
+ mpEditor->selectActionByID(mNewParentID);
+ mpParentItem = mpTreeWidgetActions->currentItem();
+
+ mpHost->getActionUnit()->reParentAction(mChildID, mNewParentID, mOldParentID, mPrevParentPosition, mPrevChildPosition);
+ mpParentItem->removeChild(mpItem);
+
+ mpPrevParentItem->insertChild(mPrevChildPosition, mpItem);
+
+ mpTreeWidgetActions->setCurrentItem(mpItem);
+}
+
+void MoveActionCommand::redo()
+{
+ if (!mpEditor) {
+ return;
+ }
+ mItemID = mpItem->data(0, Qt::UserRole).toInt();
+ mpHost->getActionUnit()->reParentAction(mChildID, mOldParentID, mNewParentID, mParentPosition, mChildPosition);
+ if (mpPrevParentItem) {
+ mpPrevParentItem->removeChild(mpItem);
+ }
+ if (mpParentItem) {
+ const int count = mpParentItem->childCount();
+ mpParentItem->insertChild(count <= 0 ? 0 : count, mpItem);
+ }
+ mpHost->getActionUnit()->updateToolbar();
+
+ setText(QObject::tr("Move action"));
+}
+
+ActiveToggleActionEditedCommand::ActiveToggleActionEditedCommand(Host* pHost, dlgActionMainArea* actionsMainArea, QUndoCommand* parent) : QUndoCommand(parent), mpHost(pHost)
+{
+ mpActionsMainArea = actionsMainArea;
+}
+
+void ActiveToggleActionEditedCommand::undo()
+{
+ if (!mpItem) {
+ return;
+ }
+
+ mpEditor->selectActionByID(mItemID);
+ mpItem = mpTreeWidgetActions->currentItem();
+
+ mpItemAction->setIsActive(mPrevActionToggle);
+ mpItemAction->setDataChanged();
+ mpEditor->slot_saveSelectedItem(mpItem);
+}
+
+void ActiveToggleActionEditedCommand::redo()
+{
+ if (!mpItem) {
+ return;
+ }
+ mItemID = mpItem->data(0, Qt::UserRole).toInt();
+ mpEditor->selectActionByID(mItemID);
+ mpItem = mpTreeWidgetActions->currentItem();
+
+ mpItemAction->setIsActive(mActionToggle);
+ mpItemAction->setDataChanged();
+ mpEditor->slot_saveSelectedItem(mpItem);
+ setText(QObject::tr("Edit action toggle"));
+}
+
+ActionNameTextEditedCommand::ActionNameTextEditedCommand(Host* pHost, dlgActionMainArea* actionsMainArea, QUndoCommand* parent) : QUndoCommand(parent), mpHost(pHost)
+{
+ mpActionsMainArea = actionsMainArea;
+}
+
+void ActionNameTextEditedCommand::undo()
+{
+ if (!mpItem) {
+ return;
+ }
+
+ mpEditor->selectActionByID(mItemID);
+ mpItem = mpTreeWidgetActions->currentItem();
+
+ mpActionsMainArea->lineEdit_action_name->blockSignals(true);
+ mpActionsMainArea->lineEdit_action_name->setText(mPrevActionName);
+ mpActionsMainArea->lineEdit_action_name->blockSignals(false);
+}
+
+void ActionNameTextEditedCommand::redo()
+{
+ if (!mpItem) {
+ return;
+ }
+ mItemID = mpItem->data(0, Qt::UserRole).toInt();
+ mpEditor->selectActionByID(mItemID);
+ mpItem = mpTreeWidgetActions->currentItem();
+
+ mpActionsMainArea->lineEdit_action_name->blockSignals(true);
+ mpActionsMainArea->lineEdit_action_name->setText(mActionName);
+ mpActionsMainArea->lineEdit_action_name->blockSignals(false);
+ setText(QObject::tr("Edit action name"));
+}
+
+ActionButtonRotationEditedCommand::ActionButtonRotationEditedCommand(Host* pHost, dlgActionMainArea* actionsMainArea, QUndoCommand* parent) : QUndoCommand(parent), mpHost(pHost)
+{
+ mpActionsMainArea = actionsMainArea;
+}
+
+void ActionButtonRotationEditedCommand::undo()
+{
+ if (!mpItem) {
+ return;
+ }
+
+ mpEditor->selectActionByID(mItemID);
+ mpItem = mpTreeWidgetActions->currentItem();
+
+ mpActionsMainArea->comboBox_action_button_rotation->blockSignals(true);
+ mpActionsMainArea->comboBox_action_button_rotation->setCurrentIndex(mPrevRotation);
+ mpActionsMainArea->comboBox_action_button_rotation->blockSignals(false);
+}
+
+void ActionButtonRotationEditedCommand::redo()
+{
+ if (!mpItem) {
+ return;
+ }
+ mItemID = mpItem->data(0, Qt::UserRole).toInt();
+ mpEditor->selectActionByID(mItemID);
+ mpItem = mpTreeWidgetActions->currentItem();
+
+ mpActionsMainArea->comboBox_action_button_rotation->blockSignals(true);
+ mpActionsMainArea->comboBox_action_button_rotation->setCurrentIndex(mRotation);
+ mpActionsMainArea->comboBox_action_button_rotation->blockSignals(false);
+ setText(QObject::tr("Edit action rotation"));
+}
+
+ActionButtonCheckboxEditedCommand::ActionButtonCheckboxEditedCommand(Host* pHost, dlgActionMainArea* actionsMainArea, QUndoCommand* parent) : QUndoCommand(parent), mpHost(pHost)
+{
+ mpActionsMainArea = actionsMainArea;
+}
+
+void ActionButtonCheckboxEditedCommand::undo()
+{
+ if (!mpItem) {
+ return;
+ }
+
+ mpEditor->selectActionByID(mItemID);
+ mpItem = mpTreeWidgetActions->currentItem();
+
+ mpActionsMainArea->checkBox_action_button_isPushDown->blockSignals(true);
+ mpActionsMainArea->checkBox_action_button_isPushDown->setChecked(mPrevIsPushDown);
+ mpActionsMainArea->checkBox_action_button_isPushDown->blockSignals(false);
+ if (mPrevIsPushDown) {
+ mpActionsMainArea->lineEdit_action_button_command_up->show();
+ mpActionsMainArea->label_action_button_command_up->show();
+ mpActionsMainArea->label_action_button_command_down->setText(mpEditor->tr("Command (down):"));
+ } else {
+ mpActionsMainArea->lineEdit_action_button_command_up->hide();
+ mpActionsMainArea->label_action_button_command_up->hide();
+ mpActionsMainArea->label_action_button_command_down->setText(mpEditor->tr("Command:"));
+ }
+}
+
+void ActionButtonCheckboxEditedCommand::redo()
+{
+ if (!mpItem) {
+ return;
+ }
+ mItemID = mpItem->data(0, Qt::UserRole).toInt();
+ mpEditor->selectActionByID(mItemID);
+ mpItem = mpTreeWidgetActions->currentItem();
+
+ mpActionsMainArea->checkBox_action_button_isPushDown->blockSignals(true);
+ mpActionsMainArea->checkBox_action_button_isPushDown->setChecked(mIsPushDown);
+ mpActionsMainArea->checkBox_action_button_isPushDown->blockSignals(false);
+ if (mIsPushDown) {
+ mpActionsMainArea->lineEdit_action_button_command_up->show();
+ mpActionsMainArea->label_action_button_command_up->show();
+ mpActionsMainArea->label_action_button_command_down->setText(mpEditor->tr("Command (down):"));
+ } else {
+ mpActionsMainArea->lineEdit_action_button_command_up->hide();
+ mpActionsMainArea->label_action_button_command_up->hide();
+ mpActionsMainArea->label_action_button_command_down->setText(mpEditor->tr("Command:"));
+ }
+ setText(QObject::tr("Edit action pushdown"));
+}
+
+ActionCommandDownTextEditedCommand::ActionCommandDownTextEditedCommand(Host* pHost, dlgActionMainArea* actionsMainArea, QUndoCommand* parent) : QUndoCommand(parent), mpHost(pHost)
+{
+ mpActionsMainArea = actionsMainArea;
+}
+
+void ActionCommandDownTextEditedCommand::undo()
+{
+ if (!mpItem) {
+ return;
+ }
+ mItemID = mpItem->data(0, Qt::UserRole).toInt();
+ mpEditor->selectActionByID(mItemID);
+ mpItem = mpTreeWidgetActions->currentItem();
+
+ mpActionsMainArea->lineEdit_action_button_command_down->blockSignals(true);
+ mpActionsMainArea->lineEdit_action_button_command_down->setText(mPrevCommandDown);
+ mpActionsMainArea->lineEdit_action_button_command_down->blockSignals(false);
+}
+
+void ActionCommandDownTextEditedCommand::redo()
+{
+ if (!mpItem) {
+ return;
+ }
+ mItemID = mpItem->data(0, Qt::UserRole).toInt();
+ mpEditor->selectActionByID(mItemID);
+ mpItem = mpTreeWidgetActions->currentItem();
+
+ mpActionsMainArea->lineEdit_action_button_command_down->blockSignals(true);
+ mpActionsMainArea->lineEdit_action_button_command_down->setText(mCommandDown);
+ mpActionsMainArea->lineEdit_action_button_command_down->blockSignals(false);
+ setText(QObject::tr("Edit action down"));
+}
+
+ActionCommandUpTextEditedCommand::ActionCommandUpTextEditedCommand(Host* pHost, dlgActionMainArea* actionsMainArea, QUndoCommand* parent) : QUndoCommand(parent), mpHost(pHost)
+{
+ mpActionsMainArea = actionsMainArea;
+}
+
+void ActionCommandUpTextEditedCommand::undo()
+{
+ if (!mpItem) {
+ return;
+ }
+
+ mpEditor->selectActionByID(mItemID);
+ mpItem = mpTreeWidgetActions->currentItem();
+
+ mpActionsMainArea->lineEdit_action_button_command_up->blockSignals(true);
+ mpActionsMainArea->lineEdit_action_button_command_up->setText(mPrevCommandUp);
+ mpActionsMainArea->lineEdit_action_button_command_up->blockSignals(false);
+}
+
+void ActionCommandUpTextEditedCommand::redo()
+{
+ if (!mpItem) {
+ return;
+ }
+ mItemID = mpItem->data(0, Qt::UserRole).toInt();
+ mpEditor->selectActionByID(mItemID);
+ mpItem = mpTreeWidgetActions->currentItem();
+
+ mpActionsMainArea->lineEdit_action_button_command_up->blockSignals(true);
+ mpActionsMainArea->lineEdit_action_button_command_up->setText(mCommandUp);
+ mpActionsMainArea->lineEdit_action_button_command_up->blockSignals(false);
+ setText(QObject::tr("Edit action up"));
+}
+
+ActionCssTextEditedCommand::ActionCssTextEditedCommand(Host* pHost, dlgActionMainArea* actionsMainArea, QUndoCommand* parent) : QUndoCommand(parent), mpHost(pHost)
+{
+ mpActionsMainArea = actionsMainArea;
+}
+
+int ActionCssTextEditedCommand::id() const
+{
+ const quint16 id = reinterpret_cast<uintptr_t>(typeid(this).name());
+ return id;
+}
+
+bool ActionCssTextEditedCommand::mergeWith(const QUndoCommand* other)
+{
+ QString text = static_cast<const ActionCssTextEditedCommand*>(other)->mActionCss;
+ if (text.length() < mActionCss.length()) {
+ mActionCss = text;
+ return false;
+ }
+ QTextCursor cursor = mpActionsMainArea->plainTextEdit_action_css->textCursor();
+ if (cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor)) {
+ QString selectedChar = cursor.selectedText();
+ if (selectedChar == QChar::ParagraphSeparator || selectedChar == QChar::Space || selectedChar == QChar::CarriageReturn || selectedChar == QChar::LineFeed) {
+ return false;
+ }
+ }
+ mActionCss = text;
+ return true;
+}
greptile
logic: Potential integer overflow in mergeWith() when casting pointer to quint16
diff block
+package main
+
+import "fmt"
+
+func binarySearchV1(arrPtr *[]int, target int) bool {
+ startIdx := 0
+ endIdx := len(*arrPtr) - 1
+
+ for startIdx <= endIdx {
+ midIdx := (startIdx + endIdx) / 2
greptile
The overflow issue occurs when dealing with very large arrays where the sum of `startIdx` and `endIdx` could exceed the maximum value that can be stored in an integer type.
In Go, integers have a fixed size (like int32 or int64 depending on your system). When you add two large integers that result in a value larger than what can be represented by the integer type, it causes an integer overflow.
For example, if you're using int32 (which has a maximum value of 2,147,483,647) and both `startIdx` and `endIdx` are close to this maximum value, their sum would overflow before the division happens.
The alternative formula `startIdx + (endIdx - startIdx) / 2` mathematically simplifies to the same result but avoids the potential overflow:
1. It first calculates the distance between `endIdx` and `startIdx`
2. Divides this distance by 2 (getting half the distance)
3. Adds this half-distance to `startIdx`
This approach never exceeds the range of the original indices, preventing any possible overflow.
suggested fix
+ midIdx := startIdx + (endIdx - startIdx) / 2
diff block
+#include "gmt-lib.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <unistd.h>
+#include <limits.h>
+#include <time.h>
+#include <sys/time.h>
+
+unsigned int parse_int(char *argument) {
+ unsigned long int number = 0;
+ char *endptr;
+
+ errno = 0; // Reset errno before the call
+ number = strtoul(argument, &endptr, 10);
+
+ if (errno == ERANGE && (number == LONG_MAX || number == LONG_MIN)) {
+ fprintf(stderr, "Error: Could not parse -i argument - Number out of range\n");
+ exit(1);
+ } else if (errno != 0 && number == 0) {
+ fprintf(stderr, "Error: Could not parse -i argument - Invalid number\n");
+ exit(1);
+ } else if (endptr == argument) {
+ fprintf(stderr, "Error: Could not parse -i argument - No digits were found\n");
+ exit(1);
+ } else if (*endptr != '\0') {
+ fprintf(stderr, "Error: Could not parse -i argument - Invalid characters after number\n");
+ exit(1);
+ }
+
+ return number;
+}
+
+void get_time_offset(struct timespec *offset) {
+ struct timespec realtime, monotonic;
+
+ if (clock_gettime(CLOCK_REALTIME, &realtime) != 0) {
+ perror("clock_gettime CLOCK_REALTIME");
+ exit(EXIT_FAILURE);
+ }
+ if (clock_gettime(CLOCK_MONOTONIC_RAW, &monotonic) != 0) {
+ perror("clock_gettime CLOCK_MONOTONIC_RAW");
+ exit(EXIT_FAILURE);
+ }
+ offset->tv_sec = realtime.tv_sec - monotonic.tv_sec;
+ offset->tv_nsec = realtime.tv_nsec - monotonic.tv_nsec;
+ if (offset->tv_nsec < 0) {
+ offset->tv_sec -= 1;
+ offset->tv_nsec += 1000000000L;
+ }
+}
+
+void get_adjusted_time(struct timeval *adjusted, struct timespec *offset) {
+ struct timespec now_monotonic;
+ if (clock_gettime(CLOCK_MONOTONIC_RAW, &now_monotonic) != 0) {
+ perror("clock_gettime CLOCK_MONOTONIC_RAW");
+ exit(EXIT_FAILURE);
+ }
+
+ // Convert to microseconds precision
+ adjusted->tv_sec = now_monotonic.tv_sec + offset->tv_sec;
+ adjusted->tv_usec = (now_monotonic.tv_nsec / 1000) + (offset->tv_nsec / 1000);
+
greptile
logic: potential integer overflow if (now_monotonic.tv_nsec / 1000 + offset->tv_nsec / 1000) exceeds LONG_MAX
Want to avoid this bug in your codebase? Try Greptile.
Avoid this bug!