// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. #include "pch.h" #include "HistoryViewModel.h" #include "Common/TraceLogger.h" #include "Common/LocalizationStringUtil.h" #include "Common/LocalizationSettings.h" using namespace CalculatorApp; using namespace CalculatorApp::Common; using namespace CalculatorApp::Common::Automation; using namespace CalculatorApp::ViewModel; using namespace Platform; using namespace std; using namespace Windows::Foundation; using namespace Windows::Storage; using namespace Windows::Storage::Streams; using namespace Windows::Security::Cryptography; using namespace Windows::Foundation::Collections; static StringReference HistoryVectorLengthKey{ L"HistoryVectorLength" }; namespace CalculatorApp::ViewModel::HistoryResourceKeys { StringReference HistoryCleared(L"HistoryList_Cleared"); } HistoryViewModel::HistoryViewModel(_In_ CalculationManager::CalculatorManager* calculatorManager) : m_calculatorManager(calculatorManager) , m_localizedHistoryCleared(nullptr) { AreHistoryShortcutsEnabled = true; Items = ref new Platform::Collections::Vector(); ItemSize = 0; } void HistoryViewModel::RestoreCompleteHistory() { RestoreHistory(CalculationManager::CALCULATOR_MODE::CM_STD); RestoreHistory(CalculationManager::CALCULATOR_MODE::CM_SCI); } // this will reload Items with the history list based on current mode void HistoryViewModel::ReloadHistory(_In_ ViewMode currentMode) { if (currentMode == ViewMode::Standard) { m_currentMode = CalculationManager::CALCULATOR_MODE::CM_STD; } else if (currentMode == ViewMode::Scientific) { m_currentMode = CalculationManager::CALCULATOR_MODE::CM_SCI; } else { return; } auto historyListModel = m_calculatorManager->GetHistoryItems(m_currentMode); auto historyListVM = ref new Platform::Collections::Vector(); const auto& localizer = LocalizationSettings::GetInstance(); if (historyListModel.size() > 0) { for (auto ritr = historyListModel.rbegin(); ritr != historyListModel.rend(); ++ritr) { wstring expression = (*ritr)->historyItemVector.expression; wstring result = (*ritr)->historyItemVector.result; localizer.LocalizeDisplayValue(&expression); localizer.LocalizeDisplayValue(&result); auto item = ref new HistoryItemViewModel( ref new Platform::String(expression.c_str()), ref new Platform::String(result.c_str()), (*ritr)->historyItemVector.spTokens, (*ritr)->historyItemVector.spCommands); historyListVM->Append(item); } } Items = historyListVM; UpdateItemSize(); } void HistoryViewModel::OnHistoryItemAdded(_In_ unsigned int addedItemIndex) { auto newItem = m_calculatorManager->GetHistoryItem(addedItemIndex); const auto& localizer = LocalizationSettings::GetInstance(); wstring expression = newItem->historyItemVector.expression; wstring result = newItem->historyItemVector.result; localizer.LocalizeDisplayValue(&expression); localizer.LocalizeDisplayValue(&result); auto item = ref new HistoryItemViewModel( ref new Platform::String(expression.c_str()), ref new Platform::String(result.c_str()), newItem->historyItemVector.spTokens, newItem->historyItemVector.spCommands); // check if we have not hit the max items if (Items->Size >= m_calculatorManager->MaxHistorySize()) { // this means the item already exists Items->RemoveAt(Items->Size - 1); } assert(addedItemIndex <= m_calculatorManager->MaxHistorySize() && addedItemIndex >= 0); Items->InsertAt(0, item); UpdateItemSize(); SaveHistory(); } void HistoryViewModel::SetCalculatorDisplay(CalculatorDisplay& calculatorDisplay) { WeakReference historyViewModel(this); calculatorDisplay.SetHistoryCallback(historyViewModel); } void HistoryViewModel::ShowItem(_In_ HistoryItemViewModel ^ e) { unsigned int index; Items->IndexOf(e, &index); TraceLogger::GetInstance().LogHistoryItemLoad((ViewMode)m_currentMode, ItemSize, (int)(index)); HistoryItemClicked(e); } void HistoryViewModel::DeleteItem(_In_ HistoryItemViewModel ^ e) { uint32_t itemIndex; if (Items->IndexOf(e, &itemIndex)) { if (m_calculatorManager->RemoveHistoryItem(itemIndex)) { // Keys for the history container are index based. // SaveHistory() re-inserts the items anyway, so it's faster to just clear out the container. CalculationManager::CALCULATOR_MODE currentMode = m_currentMode; ApplicationDataContainer ^ historyContainer = GetHistoryContainer(currentMode); historyContainer->Values->Clear(); Items->RemoveAt(itemIndex); UpdateItemSize(); SaveHistory(); } } } void HistoryViewModel::OnHideCommand(_In_ Platform::Object ^ e) { // added at VM layer so that the views do not have to individually raise events HideHistoryClicked(); } void HistoryViewModel::OnClearCommand(_In_ Platform::Object ^ e) { if (AreHistoryShortcutsEnabled == true) { m_calculatorManager->ClearHistory(); if (Items->Size > 0) { CalculationManager::CALCULATOR_MODE currentMode = m_currentMode; ClearHistoryContainer(currentMode); Items->Clear(); UpdateItemSize(); } MakeHistoryClearedNarratorAnnouncement(HistoryResourceKeys::HistoryCleared, m_localizedHistoryCleared); } } // this method restores history vector per mode void HistoryViewModel::RestoreHistory(_In_ CalculationManager::CALCULATOR_MODE cMode) { ApplicationDataContainer ^ historyContainer = GetHistoryContainer(cMode); std::shared_ptr>> historyVector = std::make_shared>>(); auto historyVectorLength = static_cast(historyContainer->Values->Lookup(HistoryVectorLengthKey)); bool failure = false; if (historyVectorLength > 0) { for (int i = 0; i < historyVectorLength; ++i) { try { // deserialize each item auto item = DeserializeHistoryItem(i.ToString(), historyContainer); std::shared_ptr Item = std::make_shared(item); historyVector->push_back(Item); } catch (Platform::Exception ^ e) { failure = true; break; } } if (!failure) { // if task has been cancelled set history to 0 m_calculatorManager->SetHistory(cMode, *historyVector); // update length once again for consistency between stored number of items and length UpdateHistoryVectorLength(static_cast(historyVector->size()), cMode); } else { // in case of failure do not show any item UpdateHistoryVectorLength(0, cMode); } } } Platform::String ^ HistoryViewModel::GetHistoryContainerKey(_In_ CalculationManager::CALCULATOR_MODE cMode) { Platform::ValueType ^ modeValue = static_cast(cMode); return Platform::String::Concat(modeValue->ToString(), L"_History"); } ApplicationDataContainer ^ HistoryViewModel::GetHistoryContainer(_In_ CalculationManager::CALCULATOR_MODE cMode) { ApplicationDataContainer ^ localSettings = ApplicationData::Current->LocalSettings; ApplicationDataContainer ^ historyContainer; // naming container based on mode Platform::String ^ historyContainerKey = GetHistoryContainerKey(cMode); if (localSettings->Containers->HasKey(historyContainerKey)) { historyContainer = localSettings->Containers->Lookup(historyContainerKey); } else { // create container for adding data historyContainer = localSettings->CreateContainer(historyContainerKey, ApplicationDataCreateDisposition::Always); int initialHistoryVectorLength = 0; historyContainer->Values->Insert(HistoryVectorLengthKey, initialHistoryVectorLength); } return historyContainer; } void HistoryViewModel::ClearHistoryContainer(_In_ CalculationManager::CALCULATOR_MODE cMode) { ApplicationDataContainer ^ localSettings = ApplicationData::Current->LocalSettings; localSettings->DeleteContainer(GetHistoryContainerKey(cMode)); } // this method will be used to update the history item length void HistoryViewModel::UpdateHistoryVectorLength(_In_ int newValue, _In_ CalculationManager::CALCULATOR_MODE cMode) { ApplicationDataContainer ^ historyContainer = GetHistoryContainer(cMode); historyContainer->Values->Remove(HistoryVectorLengthKey); historyContainer->Values->Insert(HistoryVectorLengthKey, newValue); } void HistoryViewModel::ClearHistory() { ClearHistoryContainer(CalculationManager::CALCULATOR_MODE::CM_STD); ClearHistoryContainer(CalculationManager::CALCULATOR_MODE::CM_SCI); } void HistoryViewModel::SaveHistory() { ApplicationDataContainer ^ historyContainer = GetHistoryContainer(m_currentMode); auto currentHistoryVector = m_calculatorManager->GetHistoryItems(m_currentMode); bool failure = false; int index = 0; Platform::String ^ serializedHistoryItem; for (auto iter = currentHistoryVector.begin(); iter != currentHistoryVector.end(); ++iter) { try { serializedHistoryItem = SerializeHistoryItem(*iter); historyContainer->Values->Insert(index.ToString(), serializedHistoryItem); } catch (Platform::Exception ^) { failure = true; break; } ++index; } if (!failure) { // insertion is successful UpdateHistoryVectorLength(static_cast(currentHistoryVector.size()), m_currentMode); } else { UpdateHistoryVectorLength(0, m_currentMode); } } // this serializes a history item into a base64 encoded string Platform::String ^ HistoryViewModel::SerializeHistoryItem(_In_ std::shared_ptr const& item) { DataWriter ^ writer = ref new DataWriter(); auto expr = item->historyItemVector.expression; auto result = item->historyItemVector.result; auto platformExpr = ref new Platform::String(expr.c_str()); writer->WriteUInt32(writer->MeasureString(platformExpr)); writer->WriteString(platformExpr); auto platformResult = ref new Platform::String(result.c_str()); writer->WriteUInt32(writer->MeasureString(platformResult)); writer->WriteString(platformResult); Utils::SerializeCommandsAndTokens(item->historyItemVector.spTokens, item->historyItemVector.spCommands, writer); IBuffer ^ buffer = writer->DetachBuffer(); if (buffer == nullptr) { throw ref new Platform::Exception(E_POINTER, ref new Platform::String(L"History Item is NULL")); } return CryptographicBuffer::EncodeToBase64String(buffer); } CalculationManager::HISTORYITEM HistoryViewModel::DeserializeHistoryItem(_In_ Platform::String ^ historyItemKey, _In_ ApplicationDataContainer ^ historyContainer) { CalculationManager::HISTORYITEM historyItem; if (historyContainer->Values->HasKey(historyItemKey)) { Object ^ historyItemValues = historyContainer->Values->Lookup(historyItemKey); if (historyItemValues == nullptr) { throw ref new Platform::Exception(E_POINTER, ref new Platform::String(L"History Item is NULL")); } String ^ historyData = safe_cast(historyItemValues); IBuffer ^ buffer = CryptographicBuffer::DecodeFromBase64String(historyData); if (buffer == nullptr) { throw ref new Platform::Exception(E_POINTER, ref new Platform::String(L"History Item is NULL")); } DataReader ^ reader = DataReader::FromBuffer(buffer); auto exprLen = reader->ReadUInt32(); auto expression = reader->ReadString(exprLen); historyItem.historyItemVector.expression = expression->Data(); auto resultLen = reader->ReadUInt32(); auto result = reader->ReadString(resultLen); historyItem.historyItemVector.result = result->Data(); historyItem.historyItemVector.spCommands = Utils::DeserializeCommands(reader); historyItem.historyItemVector.spTokens = Utils::DeserializeTokens(reader); } else { throw ref new Platform::Exception(E_ACCESSDENIED, ref new Platform::String(L"History Item not found")); } return historyItem; } bool HistoryViewModel::IsValid(_In_ CalculationManager::HISTORYITEM item) { return ( !item.historyItemVector.expression.empty() && !item.historyItemVector.result.empty() && (bool)item.historyItemVector.spCommands && (bool)item.historyItemVector.spTokens); } void HistoryViewModel::UpdateItemSize() { ItemSize = Items->Size; } void HistoryViewModel::MakeHistoryClearedNarratorAnnouncement(String ^ resourceKey, String ^ &formatVariable) { String ^ announcement = LocalizationStringUtil::GetLocalizedNarratorAnnouncement(resourceKey, formatVariable); HistoryAnnouncement = CalculatorAnnouncement::GetHistoryClearedAnnouncement(announcement); }