From 1164c93622db507dfeb33b342cbb45cf4f35c3b4 Mon Sep 17 00:00:00 2001 From: StevenJW Date: Tue, 9 Jun 2020 21:02:14 +0100 Subject: [PATCH] Upload project. --- syski_api/.dockerignore | 9 + syski_api/.gitattributes | 63 + syski_api/.gitignore | 330 +++++ syski_api/.travis.yml | 16 + syski_api/LICENSE | 674 +++++++++++ syski_api/README.md | 1 + syski_api/Syski.sln | 37 + syski_api/[Syski.API] Dockerfile | 21 + syski_api/[Syski.WebSocket] Dockerfile | 21 + .../Controllers/BIOSsController.cs | 59 + .../Controllers/CPUsController.cs | 113 ++ .../Controllers/GPUsController.cs | 70 ++ .../Controllers/HomeController.cs | 16 + .../Controllers/MotherboardsController.cs | 69 ++ .../Controllers/OSsController.cs | 64 + .../Controllers/RAMsController.cs | 104 ++ .../Controllers/StoragesController.cs | 114 ++ .../Controllers/SystemsController.cs | 214 ++++ .../UserAuthenticationController.cs | 155 +++ syski_api/uk.co.syski.api/Models/BIOSDTO.cs | 22 + syski_api/uk.co.syski.api/Models/CPUDTO.cs | 26 + .../uk.co.syski.api/Models/CPUDataDTO.cs | 18 + syski_api/uk.co.syski.api/Models/GPUDTO.cs | 18 + .../uk.co.syski.api/Models/KillProcessDTO.cs | 16 + .../uk.co.syski.api/Models/MotherboardDTO.cs | 20 + syski_api/uk.co.syski.api/Models/OSDTO.cs | 20 + syski_api/uk.co.syski.api/Models/RAMDTO.cs | 20 + .../uk.co.syski.api/Models/RAMDataDTO.cs | 16 + .../Models/RunningProcessesDTO.cs | 30 + .../uk.co.syski.api/Models/StorageDTO.cs | 22 + .../uk.co.syski.api/Models/StorageDataDTO.cs | 26 + syski_api/uk.co.syski.api/Models/SystemDTO.cs | 29 + .../uk.co.syski.api/Models/SystemPingDTO.cs | 14 + .../uk.co.syski.api/Models/UserAuthDTO.cs | 16 + .../uk.co.syski.api/Models/UserLoginDTO.cs | 7 + .../Models/UserRefreshTokenDTO.cs | 12 + .../uk.co.syski.api/Models/UserRegisterDTO.cs | 7 + .../uk.co.syski.api/Models/UserTokenDTO.cs | 24 + syski_api/uk.co.syski.api/Program.cs | 17 + .../Services/UserTokenManager.cs | 91 ++ syski_api/uk.co.syski.api/Startup.cs | 134 ++ syski_api/uk.co.syski.api/Syski.API.csproj | 21 + .../appsettings.Development.json | 9 + syski_api/uk.co.syski.api/appsettings.json | 18 + syski_api/uk.co.syski.data/ApplicationUser.cs | 14 + .../ApplicationUserSystemCategory.cs | 17 + .../ApplicationUserSystems.cs | 23 + syski_api/uk.co.syski.data/Architecture.cs | 18 + .../uk.co.syski.data/AuthenticationToken.cs | 35 + syski_api/uk.co.syski.data/BIOSModel.cs | 19 + syski_api/uk.co.syski.data/CPUModel.cs | 23 + syski_api/uk.co.syski.data/GPUModel.cs | 19 + syski_api/uk.co.syski.data/Manufacturer.cs | 19 + .../20190428102026_Initial.Designer.cs | 1015 ++++++++++++++++ .../Migrations/20190428102026_Initial.cs | 1073 +++++++++++++++++ .../Migrations/SyskiDBContextModelSnapshot.cs | 1013 ++++++++++++++++ syski_api/uk.co.syski.data/Model.cs | 32 + .../uk.co.syski.data/MotherboardModel.cs | 21 + .../uk.co.syski.data/OperatingSystemModel.cs | 17 + syski_api/uk.co.syski.data/RAMModel.cs | 21 + .../uk.co.syski.data/StorageInterfaceType.cs | 17 + syski_api/uk.co.syski.data/StorageModel.cs | 21 + syski_api/uk.co.syski.data/Syski.Data.csproj | 37 + syski_api/uk.co.syski.data/SyskiDBContext.cs | 366 ++++++ syski_api/uk.co.syski.data/System.cs | 52 + syski_api/uk.co.syski.data/SystemBIOS.cs | 27 + syski_api/uk.co.syski.data/SystemCPU.cs | 29 + syski_api/uk.co.syski.data/SystemCPUData.cs | 21 + syski_api/uk.co.syski.data/SystemCommand.cs | 23 + syski_api/uk.co.syski.data/SystemGPU.cs | 23 + .../uk.co.syski.data/SystemMotherboard.cs | 21 + syski_api/uk.co.syski.data/SystemOS.cs | 27 + syski_api/uk.co.syski.data/SystemPingData.cs | 19 + syski_api/uk.co.syski.data/SystemRAM.cs | 25 + syski_api/uk.co.syski.data/SystemRAMData.cs | 19 + .../SystemRunningProcesses.cs | 33 + syski_api/uk.co.syski.data/SystemStorage.cs | 27 + .../uk.co.syski.data/SystemStorageData.cs | 29 + syski_api/uk.co.syski.data/SystemType.cs | 19 + syski_api/uk.co.syski.data/SystemTypeName.cs | 17 + syski_api/uk.co.syski.websocket/Program.cs | 17 + .../Services/WebSockets/Actions/Action.cs | 13 + .../WebSockets/Actions/ActionFactory.cs | 143 +++ .../Actions/Handlers/ActionHandler.cs | 22 + .../Actions/Handlers/AuthenticationHandler.cs | 42 + .../Actions/Handlers/DefaultHandler.cs | 23 + .../Actions/Handlers/StaticBIOSHandler.cs | 99 ++ .../Actions/Handlers/StaticCPUHandler.cs | 131 ++ .../Actions/Handlers/StaticGPUHandler.cs | 110 ++ .../Handlers/StaticMotherboardHandler.cs | 106 ++ .../Handlers/StaticOperatingSystemHandler.cs | 95 ++ .../Actions/Handlers/StaticRAMHandler.cs | 120 ++ .../Actions/Handlers/StaticStorageHandler.cs | 132 ++ .../Actions/Handlers/StaticSystemHandler.cs | 125 ++ .../Handlers/SystemAuthenticationHandler.cs | 31 + .../Handlers/UserAuthenticationHandler.cs | 52 + .../Actions/Handlers/VariableCPUHandler.cs | 40 + .../Actions/Handlers/VariablePingHandler.cs | 32 + .../Actions/Handlers/VariableRAMHandler.cs | 38 + .../VariableRunningProcessesHandler.cs | 55 + .../Handlers/VariableStorageHandler.cs | 49 + .../WebSockets/Actions/Tasks/ActionTask.cs | 22 + .../WebSockets/Actions/Tasks/CommandTask.cs | 42 + .../WebSockets/Actions/Tasks/DefaultTask.cs | 23 + .../Actions/Tasks/VariablePingTask.cs | 31 + .../Services/WebSockets/IWebSocketHandler.cs | 15 + .../WebSockets/WebSocketConnection.cs | 62 + .../Services/WebSockets/WebSocketHandler.cs | 127 ++ .../Services/WebSockets/WebSocketManager.cs | 55 + .../WebSockets/WebSocketMiddleware.cs | 38 + .../WebSocketMiddlewareExtensions.cs | 15 + .../WebSockets/WebSocketTaskScheduler.cs | 70 ++ syski_api/uk.co.syski.websocket/Startup.cs | 112 ++ .../Syski.WebSocket.csproj | 16 + .../appsettings.Development.json | 9 + .../uk.co.syski.websocket/appsettings.json | 16 + syski_client_android/.gitignore | 65 + syski_client_android/.idea/misc.xml | 33 + syski_client_android/.idea/modules.xml | 9 + .../.idea/runConfigurations.xml | 12 + syski_client_android/.idea/vcs.xml | 6 + syski_client_android/.travis.yml | 0 syski_client_android/LICENSE | 674 +++++++++++ syski_client_android/README.md | 1 + syski_client_android/app/.gitignore | 1 + syski_client_android/app/build.gradle | 48 + syski_client_android/app/proguard-rules.pro | 21 + .../android/ExampleInstrumentedTest.java | 26 + .../client/android/view/CPUActivityTest.java | 20 + .../client/android/view/GPUActivityTest.java | 20 + .../client/android/view/MOBOActivityTest.java | 20 + .../client/android/view/RAMActivityTest.java | 20 + .../android/view/SettingsActivityTest.java | 28 + .../android/view/SystemListMenuTest.java | 32 + .../android/view/SystemOSActivityTest.java | 20 + .../view/SystemOverviewActivityTest.java | 28 + .../view/SystemStorageActivityTest.java | 20 + .../android/view/adapter/CPUAdapterTest.java | 12 + .../DoubleHeadedValueListAdapterTest.java | 16 + .../adapter/HeadedValueListAdapterTest.java | 16 + .../android/view/adapter/RAMAdapterTest.java | 16 + .../view/adapter/StorageAdapterTest.java | 16 + .../DoubleHeadedValueFragmentTest.java | 20 + .../fragment/HeadedValueFragmentTest.java | 20 + .../view/fragment/OverviewFragmentTest.java | 20 + .../view/graph/VariableCPULoadGraphTest.java | 12 + .../app/src/main/AndroidManifest.xml | 150 +++ .../syski/client/android/FCM/FCMService.java | 36 + .../client/android/FCM/NotifManager.java | 59 + .../client/android/model/api/APIPaths.java | 20 + .../android/model/api/VolleySingleton.java | 38 + .../android/model/api/package-info.java | 7 + .../api/requests/APIAuthorizationRequest.java | 30 + .../model/api/requests/APIRequest.java | 23 + .../api/requests/auth/APILoginRequest.java | 77 ++ .../api/requests/auth/APIRegisterRequest.java | 78 ++ .../api/requests/auth/APITokenRequest.java | 78 ++ .../requests/system/APISystemBIOSRequest.java | 60 + .../system/APISystemCPUDataRequest.java | 39 + .../requests/system/APISystemCPURequest.java | 77 ++ .../requests/system/APISystemGPURequest.java | 69 ++ .../requests/system/APISystemKillProcess.java | 54 + .../system/APISystemMotherboardRequest.java | 57 + .../APISystemOperatingSystemRequest.java | 66 + .../requests/system/APISystemPingRequest.java | 39 + .../system/APISystemProcessDataRequest.java | 39 + .../system/APISystemRAMDataRequest.java | 39 + .../requests/system/APISystemRAMRequest.java | 73 ++ .../api/requests/system/APISystemRemove.java | 34 + .../system/APISystemRestartRequest.java | 34 + .../system/APISystemShutdownRequest.java | 33 + .../system/APISystemStorageDataRequest.java | 39 + .../system/APISystemStorageRequest.java | 69 ++ .../requests/system/APISystemsRequest.java | 62 + .../android/model/database/CacheDatabase.java | 88 ++ .../android/model/database/Converters.java | 28 + .../android/model/database/SyskiCache.java | 24 + .../android/model/database/dao/BIOSDao.java | 58 + .../android/model/database/dao/CPUDao.java | 57 + .../android/model/database/dao/GPUDao.java | 53 + .../model/database/dao/MotherboardDao.java | 57 + .../database/dao/OperatingSystemDao.java | 57 + .../android/model/database/dao/RAMDao.java | 47 + .../model/database/dao/StorageDao.java | 57 + .../android/model/database/dao/SystemDao.java | 54 + .../android/model/database/dao/TypeDao.java | 20 + .../android/model/database/dao/UserDao.java | 43 + .../database/dao/linking/SystemBIOSDao.java | 41 + .../database/dao/linking/SystemCPUDao.java | 41 + .../database/dao/linking/SystemGPUDao.java | 41 + .../dao/linking/SystemMotherboardDao.java | 41 + .../database/dao/linking/SystemOSDao.java | 41 + .../database/dao/linking/SystemRAMDao.java | 41 + .../dao/linking/SystemStorageDao.java | 41 + .../database/dao/linking/SystemTypeDao.java | 11 + .../model/database/entity/BIOSEntity.java | 23 + .../model/database/entity/CPUEntity.java | 25 + .../model/database/entity/GPUEntity.java | 17 + .../database/entity/MotherboardEntity.java | 19 + .../entity/OperatingSystemEntity.java | 17 + .../model/database/entity/RAMEntity.java | 22 + .../model/database/entity/StorageEntity.java | 21 + .../model/database/entity/SystemEntity.java | 23 + .../model/database/entity/TypeEntity.java | 13 + .../model/database/entity/UserEntity.java | 23 + .../database/entity/data/CPUDataEntity.java | 40 + .../database/entity/data/RAMDataEntity.java | 37 + .../entity/data/StorageDataEntity.java | 52 + .../entity/data/SystemProcessesEntity.java | 59 + .../entity/linking/SystemBIOSEntity.java | 39 + .../entity/linking/SystemCPUEntity.java | 40 + .../entity/linking/SystemGPUEntity.java | 40 + .../linking/SystemMotherboardEntity.java | 41 + .../entity/linking/SystemOSEntity.java | 44 + .../entity/linking/SystemRAMEntity.java | 44 + .../entity/linking/SystemStorageEntity.java | 44 + .../entity/linking/SystemTypeEntity.java | 40 + .../android/model/database/package-info.java | 7 + .../model/repository/BIOSRepository.java | 195 +++ .../model/repository/CPURepository.java | 231 ++++ .../model/repository/GPURepository.java | 225 ++++ .../model/repository/MOBORepository.java | 195 +++ .../model/repository/OSRepository.java | 239 ++++ .../model/repository/RAMRepository.java | 220 ++++ .../android/model/repository/Repository.java | 57 + .../model/repository/StorageRepository.java | 220 ++++ .../repository/SystemCPUDataRepository.java | 93 ++ .../repository/SystemProcessesRepository.java | 99 ++ .../repository/SystemRAMDataRepository.java | 92 ++ .../model/repository/SystemRepository.java | 260 ++++ .../SystemStorageDataRepository.java | 97 ++ .../model/repository/UserRepository.java | 159 +++ .../model/repository/package-info.java | 4 + .../android/model/viewmodel/ModelUtil.java | 10 + .../model/viewmodel/OperatingSystemModel.java | 37 + .../model/viewmodel/SystemBIOSModel.java | 44 + .../model/viewmodel/SystemCPUModel.java | 58 + .../model/viewmodel/SystemGPUModel.java | 28 + .../android/model/viewmodel/SystemModel.java | 80 ++ .../viewmodel/SystemMotherboardModel.java | 37 + .../model/viewmodel/SystemRAMModel.java | 72 ++ .../model/viewmodel/SystemStorageModel.java | 68 ++ .../activity/AppCompatPreferenceActivity.java | 109 ++ .../android/view/activity/BIOSActivity.java | 62 + .../android/view/activity/CPUActivity.java | 112 ++ .../android/view/activity/GPUActivity.java | 62 + .../android/view/activity/MOBOActivity.java | 75 ++ .../android/view/activity/MainActivity.java | 373 ++++++ .../view/activity/NFCReceiverActivity.java | 136 +++ .../view/activity/ProcessListActivity.java | 47 + .../android/view/activity/RAMActivity.java | 88 ++ .../view/activity/SettingsActivity.java | 339 ++++++ .../android/view/activity/SyskiActivity.java | 23 + .../android/view/activity/SystemListMenu.java | 241 ++++ .../view/activity/SystemOSActivity.java | 70 ++ .../view/activity/SystemOverviewActivity.java | 229 ++++ .../view/activity/SystemStorageActivity.java | 154 +++ .../expandablelistview/CPUAdapter.java | 136 +++ .../expandablelistview/ProcessAdapter.java | 146 +++ .../expandablelistview/RAMAdapter.java | 121 ++ .../expandablelistview/StorageAdapter.java | 121 ++ .../DoubleHeadedValueListAdapter.java | 62 + .../listview/HeadedValueListAdapter.java | 58 + .../recyclerview/SystemListAdapter.java | 106 ++ .../fragment/DoubleHeadedValueFragment.java | 62 + .../view/fragment/HeadedValueFragment.java | 54 + .../view/fragment/OverviewFragment.java | 65 + .../view/graph/VariableCPULoadGraph.java | 73 ++ .../view/graph/VariableCPUProcessesGraph.java | 72 ++ .../view/graph/VariableNetworkGraph.java | 25 + .../android/view/graph/VariableRAMGraph.java | 89 ++ .../VariableStorageByteReadWriteGraph.java | 85 ++ .../graph/VariableStorageReadWriteGraph.java | 85 ++ .../view/graph/VariableStorageTimeGraph.java | 78 ++ .../graph/VariableStorageTransfersGraph.java | 78 ++ .../view/menu/ActivityOptionsMenu.java | 12 + .../android/view/menu/SyskiOptionsMenu.java | 40 + .../view/menu/SystemListOptionsMenu.java | 31 + .../view/model/DoubleHeadedValueModel.java | 17 + .../android/view/model/HeadedValueModel.java | 13 + .../viewmodel/MotherboardViewModel.java | 33 + .../viewmodel/OperatingSystemViewModel.java | 34 + .../viewmodel/SystemBIOSViewModel.java | 33 + .../viewmodel/SystemCPUDataViewModel.java | 41 + .../android/viewmodel/SystemCPUViewModel.java | 35 + .../android/viewmodel/SystemGPUViewModel.java | 32 + .../viewmodel/SystemListViewModel.java | 42 + .../viewmodel/SystemProcessesViewModel.java | 50 + .../viewmodel/SystemRAMDataViewModel.java | 41 + .../android/viewmodel/SystemRAMViewModel.java | 34 + .../viewmodel/SystemStorageDataViewModel.java | 39 + .../viewmodel/SystemStorageViewModel.java | 34 + .../viewmodel/SystemSummaryViewModel.java | 64 + .../main/res/drawable-mdpi/syski_sys_icon.png | Bin 0 -> 30316 bytes .../drawable-v24/ic_launcher_foreground.xml | 34 + .../app/src/main/res/drawable/bios_icon.png | Bin 0 -> 1763 bytes .../res/drawable/cpu_architecture_icon.png | Bin 0 -> 1844 bytes .../src/main/res/drawable/cpu_clock_icon.png | Bin 0 -> 2084 bytes .../src/main/res/drawable/cpu_core_icon.png | Bin 0 -> 1739 bytes .../app/src/main/res/drawable/cpu_icon.png | Bin 0 -> 936 bytes .../src/main/res/drawable/cpu_thread_icon.png | Bin 0 -> 1880 bytes .../app/src/main/res/drawable/date_icon.png | Bin 0 -> 1518 bytes .../app/src/main/res/drawable/expo.png | Bin 0 -> 35552 bytes .../app/src/main/res/drawable/gpu_icon.png | Bin 0 -> 6132 bytes .../app/src/main/res/drawable/graph_icon.png | Bin 0 -> 1384 bytes .../main/res/drawable/graph_placeholder.png | Bin 0 -> 12094 bytes .../res/drawable/ic_backup_black_24dp.xml | 9 + .../app/src/main/res/drawable/ic_expo.xml | 23 + .../app/src/main/res/drawable/ic_home.xml | 9 + .../main/res/drawable/ic_info_black_24dp.xml | 9 + .../res/drawable/ic_launcher_background.xml | 170 +++ .../src/main/res/drawable/ic_menu_camera.xml | 12 + .../src/main/res/drawable/ic_menu_gallery.xml | 9 + .../src/main/res/drawable/ic_menu_manage.xml | 9 + .../src/main/res/drawable/ic_menu_send.xml | 9 + .../src/main/res/drawable/ic_menu_share.xml | 9 + .../main/res/drawable/ic_menu_slideshow.xml | 9 + .../drawable/ic_notifications_black_24dp.xml | 9 + .../main/res/drawable/ic_sync_black_24dp.xml | 9 + .../main/res/drawable/ic_usb_black_24dp.xml | 9 + .../src/main/res/drawable/logo_sys_144.png | Bin 0 -> 42140 bytes .../main/res/drawable/memory_size_icon.png | Bin 0 -> 1661 bytes .../main/res/drawable/motherboard_icon.png | Bin 0 -> 5962 bytes .../app/src/main/res/drawable/name_icon.png | Bin 0 -> 1481 bytes .../src/main/res/drawable/offline_pc_icon.png | Bin 0 -> 3003 bytes .../src/main/res/drawable/online_pc_icon.png | Bin 0 -> 3154 bytes .../app/src/main/res/drawable/pc_icon.png | Bin 0 -> 5564 bytes .../app/src/main/res/drawable/ping_icon.png | Bin 0 -> 2131 bytes .../app/src/main/res/drawable/placeholder.png | Bin 0 -> 718 bytes .../src/main/res/drawable/process_icon.png | Bin 0 -> 2258 bytes .../app/src/main/res/drawable/ram_icon.png | Bin 0 -> 5448 bytes .../src/main/res/drawable/restart_icon.png | Bin 0 -> 2031 bytes .../src/main/res/drawable/shutdown_icon.png | Bin 0 -> 2153 bytes .../src/main/res/drawable/side_nav_bar.xml | 9 + .../app/src/main/res/drawable/splash.png | Bin 0 -> 47388 bytes .../app/src/main/res/drawable/splashscrn.xml | 21 + .../src/main/res/drawable/storage_icon.png | Bin 0 -> 6297 bytes .../src/main/res/drawable/version_icon.png | Bin 0 -> 1394 bytes .../app/src/main/res/layout/activity_cpu.xml | 49 + .../app/src/main/res/layout/activity_gpu.xml | 21 + .../src/main/res/layout/activity_graph.xml | 22 + .../app/src/main/res/layout/activity_main.xml | 44 + .../main/res/layout/activity_nfcreceiver.xml | 9 + .../app/src/main/res/layout/activity_os.xml | 21 + .../main/res/layout/activity_process_list.xml | 21 + .../main/res/layout/activity_qrscan_menu.xml | 35 + .../app/src/main/res/layout/activity_ram.xml | 34 + .../src/main/res/layout/activity_storage.xml | 79 ++ .../res/layout/activity_sys_list_menu.xml | 25 + .../res/layout/activity_system_overview.xml | 61 + .../main/res/layout/app_bar_sys_list_menu.xml | 27 + .../main/res/layout/content_sys_list_menu.xml | 24 + .../layout/fragment_double_headed_value.xml | 77 ++ .../main/res/layout/fragment_headed_value.xml | 48 + .../res/layout/fragment_main_tab_login.xml | 66 + .../res/layout/fragment_main_tab_register.xml | 65 + .../src/main/res/layout/fragment_overview.xml | 44 + .../src/main/res/layout/fragment_process.xml | 89 ++ .../res/layout/nav_header_sys_list_menu.xml | 35 + .../menu/activity_sys_list_menu_drawer.xml | 22 + .../app/src/main/res/menu/appbar.xml | 18 + .../app/src/main/res/menu/menu_main.xml | 10 + .../app/src/main/res/menu/sys_list_menu.xml | 10 + .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 + .../syski_sys_icon_launcher.xml | 5 + .../syski_sys_icon_launcher_round.xml | 5 + .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 3056 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 5024 bytes .../syski_icon_launcher_foreground.png | Bin 0 -> 14316 bytes .../mipmap-hdpi/syski_sys_icon_launcher.png | Bin 0 -> 8343 bytes .../syski_sys_icon_launcher_round.png | Bin 0 -> 8343 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 2096 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 2858 bytes .../syski_icon_launcher_foreground.png | Bin 0 -> 8005 bytes .../mipmap-mdpi/syski_sys_icon_launcher.png | Bin 0 -> 4807 bytes .../syski_sys_icon_launcher_round.png | Bin 0 -> 4807 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 4569 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 7098 bytes .../syski_icon_launcher_foreground.png | Bin 0 -> 21337 bytes .../mipmap-xhdpi/syski_sys_icon_launcher.png | Bin 0 -> 12449 bytes .../syski_sys_icon_launcher_round.png | Bin 0 -> 12449 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 6464 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 10676 bytes .../syski_icon_launcher_foreground.png | Bin 0 -> 36987 bytes .../mipmap-xxhdpi/syski_sys_icon_launcher.png | Bin 0 -> 20859 bytes .../syski_sys_icon_launcher_round.png | Bin 0 -> 20859 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 9250 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 15523 bytes .../syski_icon_launcher_foreground.png | Bin 0 -> 53129 bytes .../syski_sys_icon_launcher.png | Bin 0 -> 30316 bytes .../syski_sys_icon_launcher_round.png | Bin 0 -> 30316 bytes .../app/src/main/res/values-v21/styles.xml | 8 + .../app/src/main/res/values-w820dp/dimens.xml | 6 + .../res/values/black_launcher_background.xml | 4 + .../app/src/main/res/values/colors.xml | 6 + .../app/src/main/res/values/dimens.xml | 10 + .../app/src/main/res/values/preferences.xml | 30 + .../app/src/main/res/values/strings.xml | 152 +++ .../app/src/main/res/values/styles.xml | 24 + .../app/src/main/res/xml/nfc_tech_filter.xml | 7 + .../app/src/main/res/xml/pref_api.xml | 43 + .../app/src/main/res/xml/pref_data_sync.xml | 21 + .../app/src/main/res/xml/pref_developer.xml | 14 + .../app/src/main/res/xml/pref_gen.xml | 26 + .../app/src/main/res/xml/pref_general.xml | 33 + .../app/src/main/res/xml/pref_headers.xml | 36 + .../src/main/res/xml/pref_notification.xml | 27 + .../src/main/syski_sys_icon_launcher-web.png | Bin 0 -> 105544 bytes .../model/viewmodel/ModelUtilTest.java | 30 + .../model/viewmodel/SystemRAMModelTest.java | 48 + .../viewmodel/SystemStorageModelTest.java | 47 + syski_client_android/build.gradle | 27 + syski_client_android/gradle.properties | 17 + .../gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 53636 bytes .../gradle/wrapper/gradle-wrapper.properties | 6 + syski_client_android/gradlew | 160 +++ syski_client_android/gradlew.bat | 90 ++ syski_client_android/settings.gradle | 1 + syski_monitor/.gitignore | 198 +++ syski_monitor/LICENSE | 674 +++++++++++ syski_monitor/README.md | 1 + syski_monitor/config.ini | 8 + syski_monitor/pom.xml | 62 + .../uk/co/syski/client/Action/Action.java | 109 ++ .../Action/Generic/ActionAUTHENTICATION.java | 104 ++ .../client/Action/Generic/ActionERROR.java | 31 + .../Action/Generic/ActionPROCESSES.java | 35 + .../Action/Generic/ActionVARIABLEPING.java | 25 + .../Action/Linux/ActionAUTHENTICATION.java | 12 + .../client/Action/Linux/ActionERROR.java | 12 + .../Action/Linux/ActionKILLPROCESS.java | 35 + .../client/Action/Linux/ActionPROCESSES.java | 12 + .../client/Action/Linux/ActionRESTART.java | 35 + .../client/Action/Linux/ActionSHUTDOWN.java | 35 + .../client/Action/Linux/ActionSTATICBIOS.java | 36 + .../client/Action/Linux/ActionSTATICCPU.java | 36 + .../client/Action/Linux/ActionSTATICGPU.java | 39 + .../Action/Linux/ActionSTATICMOTHERBOARD.java | 36 + .../client/Action/Linux/ActionSTATICOS.java | 36 + .../client/Action/Linux/ActionSTATICRAM.java | 35 + .../Action/Linux/ActionSTATICSTORAGE.java | 37 + .../Action/Linux/ActionSTATICSYSTEM.java | 36 + .../Action/Linux/ActionVARIABLECPU.java | 27 + .../Action/Linux/ActionVARIABLENETWORK.java | 26 + .../Action/Linux/ActionVARIABLEPING.java | 11 + .../Action/Linux/ActionVARIABLERAM.java | 26 + .../Action/Linux/ActionVARIABLESTORAGE.java | 26 + .../Action/Windows/ActionAUTHENTICATION.java | 30 + .../client/Action/Windows/ActionERROR.java | 12 + .../Action/Windows/ActionKILLPROCESS.java | 35 + .../Action/Windows/ActionPROCESSES.java | 12 + .../client/Action/Windows/ActionRESTART.java | 35 + .../client/Action/Windows/ActionSHUTDOWN.java | 35 + .../Action/Windows/ActionSTATICBIOS.java | 35 + .../Action/Windows/ActionSTATICCOMPLETE.java | 36 + .../Action/Windows/ActionSTATICCPU.java | 36 + .../Action/Windows/ActionSTATICGPU.java | 40 + .../Windows/ActionSTATICMOTHERBOARD.java | 36 + .../client/Action/Windows/ActionSTATICOS.java | 37 + .../Action/Windows/ActionSTATICRAM.java | 35 + .../Action/Windows/ActionSTATICSTORAGE.java | 37 + .../Action/Windows/ActionSTATICSYSTEM.java | 36 + .../Action/Windows/ActionVARIABLECPU.java | 27 + .../Action/Windows/ActionVARIABLENETWORK.java | 26 + .../Action/Windows/ActionVARIABLEPING.java | 13 + .../Action/Windows/ActionVARIABLERAM.java | 26 + .../Action/Windows/ActionVARIABLESTORAGE.java | 26 + .../syski/client/Collection/CMDInterface.java | 26 + .../Collection/Linux/ProcInterface.java | 25 + .../Static/Component/CPUStaticCollection.java | 23 + .../Static/Component/GPUStaticCollection.java | 19 + .../MotherboardStaticCollection.java | 24 + .../Static/Component/OSStaticCollection.java | 21 + .../Static/Component/RAMStaticCollection.java | 20 + .../Component/StorageStaticCollection.java | 29 + .../Component/SystemStaticCollection.java | 26 + .../Component/CPUVariableCollection.java | 22 + .../Component/NetworkVariableCollection.java | 25 + .../Component/RAMVariableCollection.java | 20 + .../Component/StorageVariableCollection.java | 27 + .../client/Collection/ProcessCollection.java | 35 + .../Windows/PowershellInterface.java | 53 + .../CompleteSystemStaticCollection.java | 22 + .../Component/BIOSStaticCollection.java | 21 + .../Static/Component/CPUStaticCollection.java | 53 + .../Static/Component/GPUStaticCollection.java | 18 + .../MotherboardStaticCollection.java | 34 + .../Static/Component/OSStaticCollection.java | 34 + .../Static/Component/RAMStaticCollection.java | 35 + .../Component/StorageStaticCollection.java | 28 + .../Component/SystemStaticCollection.java | 49 + .../Component/CPUVariableCollection.java | 59 + .../Component/NetworkVariableCollection.java | 120 ++ .../Component/RAMVariableCollection.java | 60 + .../Component/StorageVariableCollection.java | 239 ++++ .../Collection/Windows/WMICInterface.java | 61 + .../Configuration/APIConfiguration.java | 39 + .../Configuration/ConfigurationLoader.java | 83 ++ .../Configuration/SystemConfiguration.java | 28 + .../uk/co/syski/client/JSON/JSONResponse.java | 267 ++++ .../main/java/uk/co/syski/client/Main.java | 22 + .../client/System/CompleteSystemStatic.java | 86 ++ .../System/Components/Static/BIOSStatic.java | 50 + .../System/Components/Static/CPUStatic.java | 80 ++ .../System/Components/Static/GPUStatic.java | 28 + .../Components/Static/MotherboardStatic.java | 38 + .../Components/Static/NetworkStatic.java | 18 + .../System/Components/Static/OSStatic.java | 38 + .../System/Components/Static/RAMStatic.java | 60 + .../Components/Static/StorageStatic.java | 49 + .../Components/Static/SystemStatic.java | 98 ++ .../Components/Variable/CPUVariable.java | 29 + .../Components/Variable/NetworkVariable.java | 38 + .../Components/Variable/RAMVariable.java | 18 + .../Components/Variable/StorageVariable.java | 68 ++ .../syski/client/System/RunningProcess.java | 90 ++ .../java/uk/co/syski/client/util/Output.java | 18 + .../client/websocket/NaiveSSLContext.java | 126 ++ .../client/websocket/WebSocketClient.java | 198 +++ 520 files changed, 28059 insertions(+) create mode 100644 syski_api/.dockerignore create mode 100644 syski_api/.gitattributes create mode 100644 syski_api/.gitignore create mode 100644 syski_api/.travis.yml create mode 100644 syski_api/LICENSE create mode 100644 syski_api/README.md create mode 100644 syski_api/Syski.sln create mode 100644 syski_api/[Syski.API] Dockerfile create mode 100644 syski_api/[Syski.WebSocket] Dockerfile create mode 100644 syski_api/uk.co.syski.api/Controllers/BIOSsController.cs create mode 100644 syski_api/uk.co.syski.api/Controllers/CPUsController.cs create mode 100644 syski_api/uk.co.syski.api/Controllers/GPUsController.cs create mode 100644 syski_api/uk.co.syski.api/Controllers/HomeController.cs create mode 100644 syski_api/uk.co.syski.api/Controllers/MotherboardsController.cs create mode 100644 syski_api/uk.co.syski.api/Controllers/OSsController.cs create mode 100644 syski_api/uk.co.syski.api/Controllers/RAMsController.cs create mode 100644 syski_api/uk.co.syski.api/Controllers/StoragesController.cs create mode 100644 syski_api/uk.co.syski.api/Controllers/SystemsController.cs create mode 100644 syski_api/uk.co.syski.api/Controllers/UserAuthenticationController.cs create mode 100644 syski_api/uk.co.syski.api/Models/BIOSDTO.cs create mode 100644 syski_api/uk.co.syski.api/Models/CPUDTO.cs create mode 100644 syski_api/uk.co.syski.api/Models/CPUDataDTO.cs create mode 100644 syski_api/uk.co.syski.api/Models/GPUDTO.cs create mode 100644 syski_api/uk.co.syski.api/Models/KillProcessDTO.cs create mode 100644 syski_api/uk.co.syski.api/Models/MotherboardDTO.cs create mode 100644 syski_api/uk.co.syski.api/Models/OSDTO.cs create mode 100644 syski_api/uk.co.syski.api/Models/RAMDTO.cs create mode 100644 syski_api/uk.co.syski.api/Models/RAMDataDTO.cs create mode 100644 syski_api/uk.co.syski.api/Models/RunningProcessesDTO.cs create mode 100644 syski_api/uk.co.syski.api/Models/StorageDTO.cs create mode 100644 syski_api/uk.co.syski.api/Models/StorageDataDTO.cs create mode 100644 syski_api/uk.co.syski.api/Models/SystemDTO.cs create mode 100644 syski_api/uk.co.syski.api/Models/SystemPingDTO.cs create mode 100644 syski_api/uk.co.syski.api/Models/UserAuthDTO.cs create mode 100644 syski_api/uk.co.syski.api/Models/UserLoginDTO.cs create mode 100644 syski_api/uk.co.syski.api/Models/UserRefreshTokenDTO.cs create mode 100644 syski_api/uk.co.syski.api/Models/UserRegisterDTO.cs create mode 100644 syski_api/uk.co.syski.api/Models/UserTokenDTO.cs create mode 100644 syski_api/uk.co.syski.api/Program.cs create mode 100644 syski_api/uk.co.syski.api/Services/UserTokenManager.cs create mode 100644 syski_api/uk.co.syski.api/Startup.cs create mode 100644 syski_api/uk.co.syski.api/Syski.API.csproj create mode 100644 syski_api/uk.co.syski.api/appsettings.Development.json create mode 100644 syski_api/uk.co.syski.api/appsettings.json create mode 100644 syski_api/uk.co.syski.data/ApplicationUser.cs create mode 100644 syski_api/uk.co.syski.data/ApplicationUserSystemCategory.cs create mode 100644 syski_api/uk.co.syski.data/ApplicationUserSystems.cs create mode 100644 syski_api/uk.co.syski.data/Architecture.cs create mode 100644 syski_api/uk.co.syski.data/AuthenticationToken.cs create mode 100644 syski_api/uk.co.syski.data/BIOSModel.cs create mode 100644 syski_api/uk.co.syski.data/CPUModel.cs create mode 100644 syski_api/uk.co.syski.data/GPUModel.cs create mode 100644 syski_api/uk.co.syski.data/Manufacturer.cs create mode 100644 syski_api/uk.co.syski.data/Migrations/20190428102026_Initial.Designer.cs create mode 100644 syski_api/uk.co.syski.data/Migrations/20190428102026_Initial.cs create mode 100644 syski_api/uk.co.syski.data/Migrations/SyskiDBContextModelSnapshot.cs create mode 100644 syski_api/uk.co.syski.data/Model.cs create mode 100644 syski_api/uk.co.syski.data/MotherboardModel.cs create mode 100644 syski_api/uk.co.syski.data/OperatingSystemModel.cs create mode 100644 syski_api/uk.co.syski.data/RAMModel.cs create mode 100644 syski_api/uk.co.syski.data/StorageInterfaceType.cs create mode 100644 syski_api/uk.co.syski.data/StorageModel.cs create mode 100644 syski_api/uk.co.syski.data/Syski.Data.csproj create mode 100644 syski_api/uk.co.syski.data/SyskiDBContext.cs create mode 100644 syski_api/uk.co.syski.data/System.cs create mode 100644 syski_api/uk.co.syski.data/SystemBIOS.cs create mode 100644 syski_api/uk.co.syski.data/SystemCPU.cs create mode 100644 syski_api/uk.co.syski.data/SystemCPUData.cs create mode 100644 syski_api/uk.co.syski.data/SystemCommand.cs create mode 100644 syski_api/uk.co.syski.data/SystemGPU.cs create mode 100644 syski_api/uk.co.syski.data/SystemMotherboard.cs create mode 100644 syski_api/uk.co.syski.data/SystemOS.cs create mode 100644 syski_api/uk.co.syski.data/SystemPingData.cs create mode 100644 syski_api/uk.co.syski.data/SystemRAM.cs create mode 100644 syski_api/uk.co.syski.data/SystemRAMData.cs create mode 100644 syski_api/uk.co.syski.data/SystemRunningProcesses.cs create mode 100644 syski_api/uk.co.syski.data/SystemStorage.cs create mode 100644 syski_api/uk.co.syski.data/SystemStorageData.cs create mode 100644 syski_api/uk.co.syski.data/SystemType.cs create mode 100644 syski_api/uk.co.syski.data/SystemTypeName.cs create mode 100644 syski_api/uk.co.syski.websocket/Program.cs create mode 100644 syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Action.cs create mode 100644 syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/ActionFactory.cs create mode 100644 syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Handlers/ActionHandler.cs create mode 100644 syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Handlers/AuthenticationHandler.cs create mode 100644 syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Handlers/DefaultHandler.cs create mode 100644 syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Handlers/StaticBIOSHandler.cs create mode 100644 syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Handlers/StaticCPUHandler.cs create mode 100644 syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Handlers/StaticGPUHandler.cs create mode 100644 syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Handlers/StaticMotherboardHandler.cs create mode 100644 syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Handlers/StaticOperatingSystemHandler.cs create mode 100644 syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Handlers/StaticRAMHandler.cs create mode 100644 syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Handlers/StaticStorageHandler.cs create mode 100644 syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Handlers/StaticSystemHandler.cs create mode 100644 syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Handlers/SystemAuthenticationHandler.cs create mode 100644 syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Handlers/UserAuthenticationHandler.cs create mode 100644 syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Handlers/VariableCPUHandler.cs create mode 100644 syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Handlers/VariablePingHandler.cs create mode 100644 syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Handlers/VariableRAMHandler.cs create mode 100644 syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Handlers/VariableRunningProcessesHandler.cs create mode 100644 syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Handlers/VariableStorageHandler.cs create mode 100644 syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Tasks/ActionTask.cs create mode 100644 syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Tasks/CommandTask.cs create mode 100644 syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Tasks/DefaultTask.cs create mode 100644 syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Tasks/VariablePingTask.cs create mode 100644 syski_api/uk.co.syski.websocket/Services/WebSockets/IWebSocketHandler.cs create mode 100644 syski_api/uk.co.syski.websocket/Services/WebSockets/WebSocketConnection.cs create mode 100644 syski_api/uk.co.syski.websocket/Services/WebSockets/WebSocketHandler.cs create mode 100644 syski_api/uk.co.syski.websocket/Services/WebSockets/WebSocketManager.cs create mode 100644 syski_api/uk.co.syski.websocket/Services/WebSockets/WebSocketMiddleware.cs create mode 100644 syski_api/uk.co.syski.websocket/Services/WebSockets/WebSocketMiddlewareExtensions.cs create mode 100644 syski_api/uk.co.syski.websocket/Services/WebSockets/WebSocketTaskScheduler.cs create mode 100644 syski_api/uk.co.syski.websocket/Startup.cs create mode 100644 syski_api/uk.co.syski.websocket/Syski.WebSocket.csproj create mode 100644 syski_api/uk.co.syski.websocket/appsettings.Development.json create mode 100644 syski_api/uk.co.syski.websocket/appsettings.json create mode 100644 syski_client_android/.gitignore create mode 100644 syski_client_android/.idea/misc.xml create mode 100644 syski_client_android/.idea/modules.xml create mode 100644 syski_client_android/.idea/runConfigurations.xml create mode 100644 syski_client_android/.idea/vcs.xml create mode 100644 syski_client_android/.travis.yml create mode 100644 syski_client_android/LICENSE create mode 100644 syski_client_android/README.md create mode 100644 syski_client_android/app/.gitignore create mode 100644 syski_client_android/app/build.gradle create mode 100644 syski_client_android/app/proguard-rules.pro create mode 100644 syski_client_android/app/src/androidTest/java/uk/co/syski/client/android/ExampleInstrumentedTest.java create mode 100644 syski_client_android/app/src/androidTest/java/uk/co/syski/client/android/view/CPUActivityTest.java create mode 100644 syski_client_android/app/src/androidTest/java/uk/co/syski/client/android/view/GPUActivityTest.java create mode 100644 syski_client_android/app/src/androidTest/java/uk/co/syski/client/android/view/MOBOActivityTest.java create mode 100644 syski_client_android/app/src/androidTest/java/uk/co/syski/client/android/view/RAMActivityTest.java create mode 100644 syski_client_android/app/src/androidTest/java/uk/co/syski/client/android/view/SettingsActivityTest.java create mode 100644 syski_client_android/app/src/androidTest/java/uk/co/syski/client/android/view/SystemListMenuTest.java create mode 100644 syski_client_android/app/src/androidTest/java/uk/co/syski/client/android/view/SystemOSActivityTest.java create mode 100644 syski_client_android/app/src/androidTest/java/uk/co/syski/client/android/view/SystemOverviewActivityTest.java create mode 100644 syski_client_android/app/src/androidTest/java/uk/co/syski/client/android/view/SystemStorageActivityTest.java create mode 100644 syski_client_android/app/src/androidTest/java/uk/co/syski/client/android/view/adapter/CPUAdapterTest.java create mode 100644 syski_client_android/app/src/androidTest/java/uk/co/syski/client/android/view/adapter/DoubleHeadedValueListAdapterTest.java create mode 100644 syski_client_android/app/src/androidTest/java/uk/co/syski/client/android/view/adapter/HeadedValueListAdapterTest.java create mode 100644 syski_client_android/app/src/androidTest/java/uk/co/syski/client/android/view/adapter/RAMAdapterTest.java create mode 100644 syski_client_android/app/src/androidTest/java/uk/co/syski/client/android/view/adapter/StorageAdapterTest.java create mode 100644 syski_client_android/app/src/androidTest/java/uk/co/syski/client/android/view/fragment/DoubleHeadedValueFragmentTest.java create mode 100644 syski_client_android/app/src/androidTest/java/uk/co/syski/client/android/view/fragment/HeadedValueFragmentTest.java create mode 100644 syski_client_android/app/src/androidTest/java/uk/co/syski/client/android/view/fragment/OverviewFragmentTest.java create mode 100644 syski_client_android/app/src/androidTest/java/uk/co/syski/client/android/view/graph/VariableCPULoadGraphTest.java create mode 100644 syski_client_android/app/src/main/AndroidManifest.xml create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/FCM/FCMService.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/FCM/NotifManager.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/APIPaths.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/VolleySingleton.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/package-info.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/APIAuthorizationRequest.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/APIRequest.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/auth/APILoginRequest.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/auth/APIRegisterRequest.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/auth/APITokenRequest.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/system/APISystemBIOSRequest.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/system/APISystemCPUDataRequest.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/system/APISystemCPURequest.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/system/APISystemGPURequest.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/system/APISystemKillProcess.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/system/APISystemMotherboardRequest.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/system/APISystemOperatingSystemRequest.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/system/APISystemPingRequest.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/system/APISystemProcessDataRequest.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/system/APISystemRAMDataRequest.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/system/APISystemRAMRequest.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/system/APISystemRemove.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/system/APISystemRestartRequest.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/system/APISystemShutdownRequest.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/system/APISystemStorageDataRequest.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/system/APISystemStorageRequest.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/system/APISystemsRequest.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/CacheDatabase.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/Converters.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/SyskiCache.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/dao/BIOSDao.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/dao/CPUDao.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/dao/GPUDao.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/dao/MotherboardDao.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/dao/OperatingSystemDao.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/dao/RAMDao.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/dao/StorageDao.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/dao/SystemDao.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/dao/TypeDao.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/dao/UserDao.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/dao/linking/SystemBIOSDao.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/dao/linking/SystemCPUDao.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/dao/linking/SystemGPUDao.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/dao/linking/SystemMotherboardDao.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/dao/linking/SystemOSDao.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/dao/linking/SystemRAMDao.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/dao/linking/SystemStorageDao.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/dao/linking/SystemTypeDao.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/BIOSEntity.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/CPUEntity.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/GPUEntity.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/MotherboardEntity.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/OperatingSystemEntity.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/RAMEntity.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/StorageEntity.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/SystemEntity.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/TypeEntity.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/UserEntity.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/data/CPUDataEntity.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/data/RAMDataEntity.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/data/StorageDataEntity.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/data/SystemProcessesEntity.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/linking/SystemBIOSEntity.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/linking/SystemCPUEntity.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/linking/SystemGPUEntity.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/linking/SystemMotherboardEntity.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/linking/SystemOSEntity.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/linking/SystemRAMEntity.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/linking/SystemStorageEntity.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/linking/SystemTypeEntity.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/package-info.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/repository/BIOSRepository.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/repository/CPURepository.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/repository/GPURepository.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/repository/MOBORepository.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/repository/OSRepository.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/repository/RAMRepository.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/repository/Repository.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/repository/StorageRepository.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/repository/SystemCPUDataRepository.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/repository/SystemProcessesRepository.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/repository/SystemRAMDataRepository.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/repository/SystemRepository.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/repository/SystemStorageDataRepository.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/repository/UserRepository.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/repository/package-info.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/viewmodel/ModelUtil.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/viewmodel/OperatingSystemModel.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/viewmodel/SystemBIOSModel.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/viewmodel/SystemCPUModel.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/viewmodel/SystemGPUModel.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/viewmodel/SystemModel.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/viewmodel/SystemMotherboardModel.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/viewmodel/SystemRAMModel.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/model/viewmodel/SystemStorageModel.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/view/activity/AppCompatPreferenceActivity.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/view/activity/BIOSActivity.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/view/activity/CPUActivity.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/view/activity/GPUActivity.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/view/activity/MOBOActivity.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/view/activity/MainActivity.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/view/activity/NFCReceiverActivity.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/view/activity/ProcessListActivity.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/view/activity/RAMActivity.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/view/activity/SettingsActivity.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/view/activity/SyskiActivity.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/view/activity/SystemListMenu.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/view/activity/SystemOSActivity.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/view/activity/SystemOverviewActivity.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/view/activity/SystemStorageActivity.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/view/adapter/expandablelistview/CPUAdapter.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/view/adapter/expandablelistview/ProcessAdapter.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/view/adapter/expandablelistview/RAMAdapter.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/view/adapter/expandablelistview/StorageAdapter.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/view/adapter/listview/DoubleHeadedValueListAdapter.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/view/adapter/listview/HeadedValueListAdapter.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/view/adapter/recyclerview/SystemListAdapter.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/view/fragment/DoubleHeadedValueFragment.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/view/fragment/HeadedValueFragment.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/view/fragment/OverviewFragment.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/view/graph/VariableCPULoadGraph.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/view/graph/VariableCPUProcessesGraph.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/view/graph/VariableNetworkGraph.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/view/graph/VariableRAMGraph.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/view/graph/VariableStorageByteReadWriteGraph.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/view/graph/VariableStorageReadWriteGraph.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/view/graph/VariableStorageTimeGraph.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/view/graph/VariableStorageTransfersGraph.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/view/menu/ActivityOptionsMenu.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/view/menu/SyskiOptionsMenu.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/view/menu/SystemListOptionsMenu.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/view/model/DoubleHeadedValueModel.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/view/model/HeadedValueModel.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/viewmodel/MotherboardViewModel.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/viewmodel/OperatingSystemViewModel.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/viewmodel/SystemBIOSViewModel.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/viewmodel/SystemCPUDataViewModel.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/viewmodel/SystemCPUViewModel.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/viewmodel/SystemGPUViewModel.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/viewmodel/SystemListViewModel.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/viewmodel/SystemProcessesViewModel.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/viewmodel/SystemRAMDataViewModel.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/viewmodel/SystemRAMViewModel.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/viewmodel/SystemStorageDataViewModel.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/viewmodel/SystemStorageViewModel.java create mode 100644 syski_client_android/app/src/main/java/uk/co/syski/client/android/viewmodel/SystemSummaryViewModel.java create mode 100644 syski_client_android/app/src/main/res/drawable-mdpi/syski_sys_icon.png create mode 100644 syski_client_android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml create mode 100644 syski_client_android/app/src/main/res/drawable/bios_icon.png create mode 100644 syski_client_android/app/src/main/res/drawable/cpu_architecture_icon.png create mode 100644 syski_client_android/app/src/main/res/drawable/cpu_clock_icon.png create mode 100644 syski_client_android/app/src/main/res/drawable/cpu_core_icon.png create mode 100644 syski_client_android/app/src/main/res/drawable/cpu_icon.png create mode 100644 syski_client_android/app/src/main/res/drawable/cpu_thread_icon.png create mode 100644 syski_client_android/app/src/main/res/drawable/date_icon.png create mode 100644 syski_client_android/app/src/main/res/drawable/expo.png create mode 100644 syski_client_android/app/src/main/res/drawable/gpu_icon.png create mode 100644 syski_client_android/app/src/main/res/drawable/graph_icon.png create mode 100644 syski_client_android/app/src/main/res/drawable/graph_placeholder.png create mode 100644 syski_client_android/app/src/main/res/drawable/ic_backup_black_24dp.xml create mode 100644 syski_client_android/app/src/main/res/drawable/ic_expo.xml create mode 100644 syski_client_android/app/src/main/res/drawable/ic_home.xml create mode 100644 syski_client_android/app/src/main/res/drawable/ic_info_black_24dp.xml create mode 100644 syski_client_android/app/src/main/res/drawable/ic_launcher_background.xml create mode 100644 syski_client_android/app/src/main/res/drawable/ic_menu_camera.xml create mode 100644 syski_client_android/app/src/main/res/drawable/ic_menu_gallery.xml create mode 100644 syski_client_android/app/src/main/res/drawable/ic_menu_manage.xml create mode 100644 syski_client_android/app/src/main/res/drawable/ic_menu_send.xml create mode 100644 syski_client_android/app/src/main/res/drawable/ic_menu_share.xml create mode 100644 syski_client_android/app/src/main/res/drawable/ic_menu_slideshow.xml create mode 100644 syski_client_android/app/src/main/res/drawable/ic_notifications_black_24dp.xml create mode 100644 syski_client_android/app/src/main/res/drawable/ic_sync_black_24dp.xml create mode 100644 syski_client_android/app/src/main/res/drawable/ic_usb_black_24dp.xml create mode 100644 syski_client_android/app/src/main/res/drawable/logo_sys_144.png create mode 100644 syski_client_android/app/src/main/res/drawable/memory_size_icon.png create mode 100644 syski_client_android/app/src/main/res/drawable/motherboard_icon.png create mode 100644 syski_client_android/app/src/main/res/drawable/name_icon.png create mode 100644 syski_client_android/app/src/main/res/drawable/offline_pc_icon.png create mode 100644 syski_client_android/app/src/main/res/drawable/online_pc_icon.png create mode 100644 syski_client_android/app/src/main/res/drawable/pc_icon.png create mode 100644 syski_client_android/app/src/main/res/drawable/ping_icon.png create mode 100644 syski_client_android/app/src/main/res/drawable/placeholder.png create mode 100644 syski_client_android/app/src/main/res/drawable/process_icon.png create mode 100644 syski_client_android/app/src/main/res/drawable/ram_icon.png create mode 100644 syski_client_android/app/src/main/res/drawable/restart_icon.png create mode 100644 syski_client_android/app/src/main/res/drawable/shutdown_icon.png create mode 100644 syski_client_android/app/src/main/res/drawable/side_nav_bar.xml create mode 100644 syski_client_android/app/src/main/res/drawable/splash.png create mode 100644 syski_client_android/app/src/main/res/drawable/splashscrn.xml create mode 100644 syski_client_android/app/src/main/res/drawable/storage_icon.png create mode 100644 syski_client_android/app/src/main/res/drawable/version_icon.png create mode 100644 syski_client_android/app/src/main/res/layout/activity_cpu.xml create mode 100644 syski_client_android/app/src/main/res/layout/activity_gpu.xml create mode 100644 syski_client_android/app/src/main/res/layout/activity_graph.xml create mode 100644 syski_client_android/app/src/main/res/layout/activity_main.xml create mode 100644 syski_client_android/app/src/main/res/layout/activity_nfcreceiver.xml create mode 100644 syski_client_android/app/src/main/res/layout/activity_os.xml create mode 100644 syski_client_android/app/src/main/res/layout/activity_process_list.xml create mode 100644 syski_client_android/app/src/main/res/layout/activity_qrscan_menu.xml create mode 100644 syski_client_android/app/src/main/res/layout/activity_ram.xml create mode 100644 syski_client_android/app/src/main/res/layout/activity_storage.xml create mode 100644 syski_client_android/app/src/main/res/layout/activity_sys_list_menu.xml create mode 100644 syski_client_android/app/src/main/res/layout/activity_system_overview.xml create mode 100644 syski_client_android/app/src/main/res/layout/app_bar_sys_list_menu.xml create mode 100644 syski_client_android/app/src/main/res/layout/content_sys_list_menu.xml create mode 100644 syski_client_android/app/src/main/res/layout/fragment_double_headed_value.xml create mode 100644 syski_client_android/app/src/main/res/layout/fragment_headed_value.xml create mode 100644 syski_client_android/app/src/main/res/layout/fragment_main_tab_login.xml create mode 100644 syski_client_android/app/src/main/res/layout/fragment_main_tab_register.xml create mode 100644 syski_client_android/app/src/main/res/layout/fragment_overview.xml create mode 100644 syski_client_android/app/src/main/res/layout/fragment_process.xml create mode 100644 syski_client_android/app/src/main/res/layout/nav_header_sys_list_menu.xml create mode 100644 syski_client_android/app/src/main/res/menu/activity_sys_list_menu_drawer.xml create mode 100644 syski_client_android/app/src/main/res/menu/appbar.xml create mode 100644 syski_client_android/app/src/main/res/menu/menu_main.xml create mode 100644 syski_client_android/app/src/main/res/menu/sys_list_menu.xml create mode 100644 syski_client_android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 syski_client_android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 syski_client_android/app/src/main/res/mipmap-anydpi-v26/syski_sys_icon_launcher.xml create mode 100644 syski_client_android/app/src/main/res/mipmap-anydpi-v26/syski_sys_icon_launcher_round.xml create mode 100644 syski_client_android/app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 syski_client_android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png create mode 100644 syski_client_android/app/src/main/res/mipmap-hdpi/syski_icon_launcher_foreground.png create mode 100644 syski_client_android/app/src/main/res/mipmap-hdpi/syski_sys_icon_launcher.png create mode 100644 syski_client_android/app/src/main/res/mipmap-hdpi/syski_sys_icon_launcher_round.png create mode 100644 syski_client_android/app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 syski_client_android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png create mode 100644 syski_client_android/app/src/main/res/mipmap-mdpi/syski_icon_launcher_foreground.png create mode 100644 syski_client_android/app/src/main/res/mipmap-mdpi/syski_sys_icon_launcher.png create mode 100644 syski_client_android/app/src/main/res/mipmap-mdpi/syski_sys_icon_launcher_round.png create mode 100644 syski_client_android/app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 syski_client_android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png create mode 100644 syski_client_android/app/src/main/res/mipmap-xhdpi/syski_icon_launcher_foreground.png create mode 100644 syski_client_android/app/src/main/res/mipmap-xhdpi/syski_sys_icon_launcher.png create mode 100644 syski_client_android/app/src/main/res/mipmap-xhdpi/syski_sys_icon_launcher_round.png create mode 100644 syski_client_android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 syski_client_android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 syski_client_android/app/src/main/res/mipmap-xxhdpi/syski_icon_launcher_foreground.png create mode 100644 syski_client_android/app/src/main/res/mipmap-xxhdpi/syski_sys_icon_launcher.png create mode 100644 syski_client_android/app/src/main/res/mipmap-xxhdpi/syski_sys_icon_launcher_round.png create mode 100644 syski_client_android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 syski_client_android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 syski_client_android/app/src/main/res/mipmap-xxxhdpi/syski_icon_launcher_foreground.png create mode 100644 syski_client_android/app/src/main/res/mipmap-xxxhdpi/syski_sys_icon_launcher.png create mode 100644 syski_client_android/app/src/main/res/mipmap-xxxhdpi/syski_sys_icon_launcher_round.png create mode 100644 syski_client_android/app/src/main/res/values-v21/styles.xml create mode 100644 syski_client_android/app/src/main/res/values-w820dp/dimens.xml create mode 100644 syski_client_android/app/src/main/res/values/black_launcher_background.xml create mode 100644 syski_client_android/app/src/main/res/values/colors.xml create mode 100644 syski_client_android/app/src/main/res/values/dimens.xml create mode 100644 syski_client_android/app/src/main/res/values/preferences.xml create mode 100644 syski_client_android/app/src/main/res/values/strings.xml create mode 100644 syski_client_android/app/src/main/res/values/styles.xml create mode 100644 syski_client_android/app/src/main/res/xml/nfc_tech_filter.xml create mode 100644 syski_client_android/app/src/main/res/xml/pref_api.xml create mode 100644 syski_client_android/app/src/main/res/xml/pref_data_sync.xml create mode 100644 syski_client_android/app/src/main/res/xml/pref_developer.xml create mode 100644 syski_client_android/app/src/main/res/xml/pref_gen.xml create mode 100644 syski_client_android/app/src/main/res/xml/pref_general.xml create mode 100644 syski_client_android/app/src/main/res/xml/pref_headers.xml create mode 100644 syski_client_android/app/src/main/res/xml/pref_notification.xml create mode 100644 syski_client_android/app/src/main/syski_sys_icon_launcher-web.png create mode 100644 syski_client_android/app/src/test/java/uk/co/syski/client/android/model/viewmodel/ModelUtilTest.java create mode 100644 syski_client_android/app/src/test/java/uk/co/syski/client/android/model/viewmodel/SystemRAMModelTest.java create mode 100644 syski_client_android/app/src/test/java/uk/co/syski/client/android/model/viewmodel/SystemStorageModelTest.java create mode 100644 syski_client_android/build.gradle create mode 100644 syski_client_android/gradle.properties create mode 100644 syski_client_android/gradle/wrapper/gradle-wrapper.jar create mode 100644 syski_client_android/gradle/wrapper/gradle-wrapper.properties create mode 100644 syski_client_android/gradlew create mode 100644 syski_client_android/gradlew.bat create mode 100644 syski_client_android/settings.gradle create mode 100644 syski_monitor/.gitignore create mode 100644 syski_monitor/LICENSE create mode 100644 syski_monitor/README.md create mode 100644 syski_monitor/config.ini create mode 100644 syski_monitor/pom.xml create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/Action/Action.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/Action/Generic/ActionAUTHENTICATION.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/Action/Generic/ActionERROR.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/Action/Generic/ActionPROCESSES.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/Action/Generic/ActionVARIABLEPING.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/Action/Linux/ActionAUTHENTICATION.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/Action/Linux/ActionERROR.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/Action/Linux/ActionKILLPROCESS.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/Action/Linux/ActionPROCESSES.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/Action/Linux/ActionRESTART.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/Action/Linux/ActionSHUTDOWN.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/Action/Linux/ActionSTATICBIOS.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/Action/Linux/ActionSTATICCPU.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/Action/Linux/ActionSTATICGPU.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/Action/Linux/ActionSTATICMOTHERBOARD.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/Action/Linux/ActionSTATICOS.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/Action/Linux/ActionSTATICRAM.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/Action/Linux/ActionSTATICSTORAGE.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/Action/Linux/ActionSTATICSYSTEM.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/Action/Linux/ActionVARIABLECPU.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/Action/Linux/ActionVARIABLENETWORK.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/Action/Linux/ActionVARIABLEPING.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/Action/Linux/ActionVARIABLERAM.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/Action/Linux/ActionVARIABLESTORAGE.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/Action/Windows/ActionAUTHENTICATION.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/Action/Windows/ActionERROR.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/Action/Windows/ActionKILLPROCESS.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/Action/Windows/ActionPROCESSES.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/Action/Windows/ActionRESTART.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/Action/Windows/ActionSHUTDOWN.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/Action/Windows/ActionSTATICBIOS.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/Action/Windows/ActionSTATICCOMPLETE.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/Action/Windows/ActionSTATICCPU.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/Action/Windows/ActionSTATICGPU.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/Action/Windows/ActionSTATICMOTHERBOARD.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/Action/Windows/ActionSTATICOS.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/Action/Windows/ActionSTATICRAM.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/Action/Windows/ActionSTATICSTORAGE.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/Action/Windows/ActionSTATICSYSTEM.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/Action/Windows/ActionVARIABLECPU.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/Action/Windows/ActionVARIABLENETWORK.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/Action/Windows/ActionVARIABLEPING.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/Action/Windows/ActionVARIABLERAM.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/Action/Windows/ActionVARIABLESTORAGE.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/Collection/CMDInterface.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/Collection/Linux/ProcInterface.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/Collection/Linux/Static/Component/CPUStaticCollection.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/Collection/Linux/Static/Component/GPUStaticCollection.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/Collection/Linux/Static/Component/MotherboardStaticCollection.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/Collection/Linux/Static/Component/OSStaticCollection.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/Collection/Linux/Static/Component/RAMStaticCollection.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/Collection/Linux/Static/Component/StorageStaticCollection.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/Collection/Linux/Static/Component/SystemStaticCollection.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/Collection/Linux/Variable/Component/CPUVariableCollection.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/Collection/Linux/Variable/Component/NetworkVariableCollection.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/Collection/Linux/Variable/Component/RAMVariableCollection.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/Collection/Linux/Variable/Component/StorageVariableCollection.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/Collection/ProcessCollection.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/Collection/Windows/PowershellInterface.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/Collection/Windows/Static/CompleteSystemStaticCollection.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/Collection/Windows/Static/Component/BIOSStaticCollection.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/Collection/Windows/Static/Component/CPUStaticCollection.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/Collection/Windows/Static/Component/GPUStaticCollection.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/Collection/Windows/Static/Component/MotherboardStaticCollection.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/Collection/Windows/Static/Component/OSStaticCollection.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/Collection/Windows/Static/Component/RAMStaticCollection.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/Collection/Windows/Static/Component/StorageStaticCollection.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/Collection/Windows/Static/Component/SystemStaticCollection.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/Collection/Windows/Variable/Component/CPUVariableCollection.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/Collection/Windows/Variable/Component/NetworkVariableCollection.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/Collection/Windows/Variable/Component/RAMVariableCollection.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/Collection/Windows/Variable/Component/StorageVariableCollection.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/Collection/Windows/WMICInterface.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/Configuration/APIConfiguration.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/Configuration/ConfigurationLoader.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/Configuration/SystemConfiguration.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/JSON/JSONResponse.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/Main.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/System/CompleteSystemStatic.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/System/Components/Static/BIOSStatic.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/System/Components/Static/CPUStatic.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/System/Components/Static/GPUStatic.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/System/Components/Static/MotherboardStatic.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/System/Components/Static/NetworkStatic.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/System/Components/Static/OSStatic.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/System/Components/Static/RAMStatic.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/System/Components/Static/StorageStatic.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/System/Components/Static/SystemStatic.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/System/Components/Variable/CPUVariable.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/System/Components/Variable/NetworkVariable.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/System/Components/Variable/RAMVariable.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/System/Components/Variable/StorageVariable.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/System/RunningProcess.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/util/Output.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/websocket/NaiveSSLContext.java create mode 100644 syski_monitor/src/main/java/uk/co/syski/client/websocket/WebSocketClient.java diff --git a/syski_api/.dockerignore b/syski_api/.dockerignore new file mode 100644 index 0000000..df2e0fe --- /dev/null +++ b/syski_api/.dockerignore @@ -0,0 +1,9 @@ +.dockerignore +.env +.git +.gitignore +.vs +.vscode +*/bin +*/obj +**/.toolstarget \ No newline at end of file diff --git a/syski_api/.gitattributes b/syski_api/.gitattributes new file mode 100644 index 0000000..1ff0c42 --- /dev/null +++ b/syski_api/.gitattributes @@ -0,0 +1,63 @@ +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +* text=auto + +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +#*.cs diff=csharp + +############################################################################### +# Set the merge driver for project and solution files +# +# Merging from the command prompt will add diff markers to the files if there +# are conflicts (Merging from VS is not affected by the settings below, in VS +# the diff markers are never inserted). Diff markers may cause the following +# file extensions to fail to load in VS. An alternative would be to treat +# these files as binary and thus will always conflict and require user +# intervention with every merge. To do so, just uncomment the entries below +############################################################################### +#*.sln merge=binary +#*.csproj merge=binary +#*.vbproj merge=binary +#*.vcxproj merge=binary +#*.vcproj merge=binary +#*.dbproj merge=binary +#*.fsproj merge=binary +#*.lsproj merge=binary +#*.wixproj merge=binary +#*.modelproj merge=binary +#*.sqlproj merge=binary +#*.wwaproj merge=binary + +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +#*.jpg binary +#*.png binary +#*.gif binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.doc diff=astextplain +#*.DOC diff=astextplain +#*.docx diff=astextplain +#*.DOCX diff=astextplain +#*.dot diff=astextplain +#*.DOT diff=astextplain +#*.pdf diff=astextplain +#*.PDF diff=astextplain +#*.rtf diff=astextplain +#*.RTF diff=astextplain diff --git a/syski_api/.gitignore b/syski_api/.gitignore new file mode 100644 index 0000000..3e759b7 --- /dev/null +++ b/syski_api/.gitignore @@ -0,0 +1,330 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ +**/Properties/launchSettings.json + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ diff --git a/syski_api/.travis.yml b/syski_api/.travis.yml new file mode 100644 index 0000000..44ef8e8 --- /dev/null +++ b/syski_api/.travis.yml @@ -0,0 +1,16 @@ +language: csharp +solution: api.syski.co.uk.sln +mono: none +dotnet: 2.1.504 +install: + - dotnet restore "csharp/csharp.csproj" +script: + - dotnet build "csharp/csharp.csproj" +after_success: + - wget https://raw.githubusercontent.com/DiscordHooks/travis-ci-discord-webhook/master/send.sh + - chmod +x send.sh + - ./send.sh success $WEBHOOK_URL +after_failure: + - wget https://raw.githubusercontent.com/DiscordHooks/travis-ci-discord-webhook/master/send.sh + - chmod +x send.sh + - ./send.sh failure $WEBHOOK_URL \ No newline at end of file diff --git a/syski_api/LICENSE b/syski_api/LICENSE new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/syski_api/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + 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 3 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, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/syski_api/README.md b/syski_api/README.md new file mode 100644 index 0000000..2773d39 --- /dev/null +++ b/syski_api/README.md @@ -0,0 +1 @@ +# syski_api_csharp \ No newline at end of file diff --git a/syski_api/Syski.sln b/syski_api/Syski.sln new file mode 100644 index 0000000..fd9db93 --- /dev/null +++ b/syski_api/Syski.sln @@ -0,0 +1,37 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.28307.438 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Syski.Data", "uk.co.syski.data\Syski.Data.csproj", "{5B950C57-CE5C-488B-9EED-99530EB72FBB}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Syski.WebSocket", "uk.co.syski.websocket\Syski.WebSocket.csproj", "{32BAD1E3-CA67-498E-8A14-1FD9C7499BB2}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Syski.API", "uk.co.syski.api\Syski.API.csproj", "{6B0FE44A-793D-4742-8F22-4BCB01EC6FF5}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {5B950C57-CE5C-488B-9EED-99530EB72FBB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5B950C57-CE5C-488B-9EED-99530EB72FBB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5B950C57-CE5C-488B-9EED-99530EB72FBB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5B950C57-CE5C-488B-9EED-99530EB72FBB}.Release|Any CPU.Build.0 = Release|Any CPU + {32BAD1E3-CA67-498E-8A14-1FD9C7499BB2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {32BAD1E3-CA67-498E-8A14-1FD9C7499BB2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {32BAD1E3-CA67-498E-8A14-1FD9C7499BB2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {32BAD1E3-CA67-498E-8A14-1FD9C7499BB2}.Release|Any CPU.Build.0 = Release|Any CPU + {6B0FE44A-793D-4742-8F22-4BCB01EC6FF5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6B0FE44A-793D-4742-8F22-4BCB01EC6FF5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6B0FE44A-793D-4742-8F22-4BCB01EC6FF5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6B0FE44A-793D-4742-8F22-4BCB01EC6FF5}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {9F20A3C6-8EE3-474B-8460-2CD9F0E4EBC3} + EndGlobalSection +EndGlobal diff --git a/syski_api/[Syski.API] Dockerfile b/syski_api/[Syski.API] Dockerfile new file mode 100644 index 0000000..ae62bff --- /dev/null +++ b/syski_api/[Syski.API] Dockerfile @@ -0,0 +1,21 @@ +FROM microsoft/dotnet:2.1-aspnetcore-runtime AS base +WORKDIR /app +EXPOSE 80 +EXPOSE 443 + +FROM microsoft/dotnet:2.1-sdk AS build +WORKDIR /src +COPY ["uk.co.syski.api/Syski.API.csproj", "uk.co.syski.api/"] +COPY ["uk.co.syski.data/Syski.Data.csproj", "uk.co.syski.data/"] +RUN dotnet restore "uk.co.syski.api/Syski.API.csproj" +COPY . . +WORKDIR "/src/uk.co.syski.api" +RUN dotnet build "Syski.API.csproj" -c Release -o /app + +FROM build AS publish +RUN dotnet publish "Syski.API.csproj" -c Release -o /app + +FROM base AS final +WORKDIR /app +COPY --from=publish /app . +ENTRYPOINT ["dotnet", "Syski.API.dll"] \ No newline at end of file diff --git a/syski_api/[Syski.WebSocket] Dockerfile b/syski_api/[Syski.WebSocket] Dockerfile new file mode 100644 index 0000000..5a62e09 --- /dev/null +++ b/syski_api/[Syski.WebSocket] Dockerfile @@ -0,0 +1,21 @@ +FROM microsoft/dotnet:2.1-aspnetcore-runtime AS base +WORKDIR /app +EXPOSE 80 +EXPOSE 443 + +FROM microsoft/dotnet:2.1-sdk AS build +WORKDIR /src +COPY ["uk.co.syski.websocket/Syski.WebSocket.csproj", "uk.co.syski.websocket/"] +COPY ["uk.co.syski.data/Syski.Data.csproj", "uk.co.syski.data/"] +RUN dotnet restore "uk.co.syski.websocket/Syski.WebSocket.csproj" +COPY . . +WORKDIR "/src/uk.co.syski.websocket" +RUN dotnet build "Syski.WebSocket.csproj" -c Release -o /app + +FROM build AS publish +RUN dotnet publish "Syski.WebSocket.csproj" -c Release -o /app + +FROM base AS final +WORKDIR /app +COPY --from=publish /app . +ENTRYPOINT ["dotnet", "Syski.WebSocket.dll"] \ No newline at end of file diff --git a/syski_api/uk.co.syski.api/Controllers/BIOSsController.cs b/syski_api/uk.co.syski.api/Controllers/BIOSsController.cs new file mode 100644 index 0000000..73c2b60 --- /dev/null +++ b/syski_api/uk.co.syski.api/Controllers/BIOSsController.cs @@ -0,0 +1,59 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Syski.API.Models; +using Syski.Data; +using System; +using System.Linq; +using System.Security.Claims; + +namespace Syski.API.Controllers +{ + [ApiController] + public class BIOSsController : ControllerBase + { + + private readonly SyskiDBContext context; + + public BIOSsController(SyskiDBContext context) + { + this.context = context; + } + + [Authorize] + [HttpGet("/system/{systemId}/bios")] + public IActionResult GetBIOS(Guid systemId) + { + var applicationUserSystem = context.ApplicationUserSystems.FirstOrDefault(u => u.User.Email.Equals(((ClaimsIdentity) User.Identity).FindFirst("email").Value) && u.SystemId.Equals(systemId)); + + if (applicationUserSystem == null) + return NotFound(); + + var bios = context.SystemBIOSs.FirstOrDefault(sc => sc.SystemId.Equals(systemId)); + var biosDTO = CreateBIOSDTO(bios); + + return Ok(biosDTO); + } + + private BIOSDTO CreateBIOSDTO(SystemBIOS systemBIOS) + { + var biosModel = context.BIOSModels.Find(systemBIOS.BIOSModelId); + + var biosDTO = new BIOSDTO() + { + Id = systemBIOS.BIOSModelId, + Caption = systemBIOS.Caption, + Date = systemBIOS.Date, + Version = systemBIOS.Version + }; + + if (biosModel.ManufacturerId != null) + { + var manufacturer = context.Manufacturers.Find(biosModel.ManufacturerId); + biosDTO.ManufacturerName = manufacturer.Name; + } + + return biosDTO; + } + + } +} diff --git a/syski_api/uk.co.syski.api/Controllers/CPUsController.cs b/syski_api/uk.co.syski.api/Controllers/CPUsController.cs new file mode 100644 index 0000000..93cf66b --- /dev/null +++ b/syski_api/uk.co.syski.api/Controllers/CPUsController.cs @@ -0,0 +1,113 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Syski.API.Models; +using Syski.Data; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; + +namespace Syski.API.Controllers +{ + [ApiController] + public class CPUsController : ControllerBase + { + + private readonly SyskiDBContext context; + + public CPUsController(SyskiDBContext context) + { + this.context = context; + } + + [Authorize] + [HttpGet("/system/{systemId}/cpu")] + public IActionResult GetCPUs(Guid systemId) + { + var applicationUserSystem = context.ApplicationUserSystems.FirstOrDefault(u => u.User.Email.Equals(((ClaimsIdentity)User.Identity).FindFirst("email").Value) && u.SystemId.Equals(systemId)); + + if (applicationUserSystem == null) + return NotFound(); + + var lastUpdatedCPU = context.SystemCPUs.Where(sc => sc.SystemId.Equals(systemId)).OrderByDescending(i => i.LastUpdated).FirstOrDefault(); + DateTime? lastUpdated = (lastUpdatedCPU != null ? lastUpdatedCPU.LastUpdated : (DateTime?)null); + var CPUs = context.SystemCPUs.Where(sc => sc.SystemId.Equals(systemId) && sc.LastUpdated.Equals(lastUpdated)).ToList(); + + var CPUDTOs = new List(); + + foreach (var item in CPUs) + { + CPUDTOs.Add(CreateCPUDTO(item)); + } + + return Ok(CPUDTOs); + } + + + [Authorize] + [HttpGet("/system/{systemId}/cpu/data")] + public IActionResult GetCPUData(Guid systemId) + { + var applicationUserSystem = context.ApplicationUserSystems.FirstOrDefault(u => u.User.Email.Equals(((ClaimsIdentity) User.Identity).FindFirst("email").Value) && u.SystemId.Equals(systemId)); + + if (applicationUserSystem == null) + return NotFound(); + + var CPUsData = context.SystemCPUsData.Where(sc => sc.SystemId.Equals(systemId)).OrderByDescending(i => i.CollectionDateTime).FirstOrDefault(); + + if (CPUsData == null || CPUsData.CollectionDateTime.AddSeconds(9) < DateTime.Now) + { + return NotFound(); + } + else + { + var cpuDTOs = new List(); + cpuDTOs.Add(CreateCPUDataDTO(CPUsData)); + return Ok(cpuDTOs); + } + } + + private CPUDTO CreateCPUDTO(SystemCPU systemCPU) + { + var cpuModel = context.CPUModels.Find(systemCPU.CPUModelID); + + var cpuDTO = new CPUDTO() + { + Id = systemCPU.CPUModelID, + ClockSpeed = systemCPU.ClockSpeed, + CoreCount = systemCPU.CoreCount, + ThreadCount = systemCPU.ThreadCount + }; + + if (cpuModel.ModelId != null) + { + var model = context.Models.Find(cpuModel.ModelId); + cpuDTO.ModelName = model.Name; + if (model.ManufacturerId != null) + { + var manufacturer = context.Manufacturers.Find(model.ManufacturerId); + cpuDTO.ManufacturerName = manufacturer.Name; + } + } + + if (cpuModel.ArchitectureId != null) + { + var architecture = context.Architectures.Find(cpuModel.ArchitectureId); + cpuDTO.ArchitectureName = architecture.Name; + } + + return cpuDTO; + } + + private CPUDataDTO CreateCPUDataDTO(SystemCPUData systemCPUData) + { + return new CPUDataDTO + { + Processes = systemCPUData.Processes, + Load = systemCPUData.Load, + CollectionDateTime = systemCPUData.CollectionDateTime + }; + } + + } +} diff --git a/syski_api/uk.co.syski.api/Controllers/GPUsController.cs b/syski_api/uk.co.syski.api/Controllers/GPUsController.cs new file mode 100644 index 0000000..0fc6715 --- /dev/null +++ b/syski_api/uk.co.syski.api/Controllers/GPUsController.cs @@ -0,0 +1,70 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Syski.API.Models; +using Syski.Data; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; + +namespace Syski.API.Controllers +{ + [ApiController] + public class GPUsController : ControllerBase + { + + private readonly SyskiDBContext context; + + public GPUsController(SyskiDBContext context) + { + this.context = context; + } + + [Authorize] + [HttpGet("/system/{systemId}/gpu")] + public IActionResult GetGPUs(Guid systemId) + { + var applicationUserSystem = context.ApplicationUserSystems.FirstOrDefault(u => u.User.Email.Equals(((ClaimsIdentity) User.Identity).FindFirst("email").Value) && u.SystemId.Equals(systemId)); + + if (applicationUserSystem == null) + return NotFound(); + + var lastUpdatedGPU = context.SystemGPUs.Where(sc => sc.SystemId.Equals(systemId)).OrderByDescending(i => i.LastUpdated).FirstOrDefault(); + DateTime? lastUpdated = (lastUpdatedGPU != null ? lastUpdatedGPU.LastUpdated : (DateTime?)null); + var GPUs = context.SystemGPUs.Where(sc => sc.SystemId.Equals(systemId) && sc.LastUpdated.Equals(lastUpdated)).ToList(); + + var GPUDTOs = new List(); + + foreach (var item in GPUs) + { + GPUDTOs.Add(CreateGPUDTO(item)); + } + + return Ok(GPUDTOs); + } + + private GPUDTO CreateGPUDTO(SystemGPU systemGPU) + { + var gpuModel = context.GPUModels.Find(systemGPU.GPUModelId); + + var gpuDTO = new GPUDTO() + { + Id = systemGPU.GPUModelId + }; + + if (gpuModel.ModelId != null) + { + var model = context.Models.Find(gpuModel.ModelId); + gpuDTO.ModelName = model.Name; + if (model.ManufacturerId != null) + { + var manufacturer = context.Manufacturers.Find(model.ManufacturerId); + gpuDTO.ManufacturerName = manufacturer.Name; + } + } + + return gpuDTO; + } + + } +} diff --git a/syski_api/uk.co.syski.api/Controllers/HomeController.cs b/syski_api/uk.co.syski.api/Controllers/HomeController.cs new file mode 100644 index 0000000..2f0bd8d --- /dev/null +++ b/syski_api/uk.co.syski.api/Controllers/HomeController.cs @@ -0,0 +1,16 @@ +using Microsoft.AspNetCore.Mvc; + +namespace Syski.API.Controllers +{ + [ApiController] + public class HomeController : ControllerBase + { + + [HttpGet("/")] + public ActionResult Get() + { + return Ok(); + } + + } +} diff --git a/syski_api/uk.co.syski.api/Controllers/MotherboardsController.cs b/syski_api/uk.co.syski.api/Controllers/MotherboardsController.cs new file mode 100644 index 0000000..424dd2b --- /dev/null +++ b/syski_api/uk.co.syski.api/Controllers/MotherboardsController.cs @@ -0,0 +1,69 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Syski.API.Models; +using Syski.Data; +using System; +using System.Linq; +using System.Security.Claims; + +namespace Syski.API.Controllers +{ + [ApiController] + public class MotherboardsController : ControllerBase + { + + private readonly SyskiDBContext context; + + public MotherboardsController(SyskiDBContext context) + { + this.context = context; + } + + [Authorize] + [HttpGet("/system/{systemId}/motherboard")] + public IActionResult GetMotherboard(Guid systemId) + { + var applicationUserSystem = context.ApplicationUserSystems.FirstOrDefault(u => u.User.Email.Equals(((ClaimsIdentity) User.Identity).FindFirst("email").Value) && u.SystemId.Equals(systemId)); + + if (applicationUserSystem == null) + return NotFound(); + + var motherboard = context.SystemMotherboards.FirstOrDefault(sc => sc.SystemId.Equals(systemId)); + var MotherboardDTO = CreateMotherboardDTO(motherboard); + + return Ok(MotherboardDTO); + } + + private MotherboardDTO CreateMotherboardDTO(SystemMotherboard systemMotherboard) + { + if (systemMotherboard != null) + { + var motherboardModel = context.MotherboardModels.Find(systemMotherboard.MotherboardModelId); + + var motherboardDTO = new MotherboardDTO() + { + Id = systemMotherboard.MotherboardModelId, + Version = motherboardModel.Version + }; + + if (motherboardModel.ModelId != null) + { + var model = context.Models.Find(motherboardModel.ModelId); + motherboardDTO.ModelName = model.Name; + if (model.ManufacturerId != null) + { + var manufacturer = context.Manufacturers.Find(model.ManufacturerId); + motherboardDTO.ManufacturerName = manufacturer.Name; + } + } + + return motherboardDTO; + } + else + { + return new MotherboardDTO(); + } + } + + } +} diff --git a/syski_api/uk.co.syski.api/Controllers/OSsController.cs b/syski_api/uk.co.syski.api/Controllers/OSsController.cs new file mode 100644 index 0000000..e48b2f3 --- /dev/null +++ b/syski_api/uk.co.syski.api/Controllers/OSsController.cs @@ -0,0 +1,64 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Syski.API.Models; +using Syski.Data; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; + +namespace Syski.API.Controllers +{ + [ApiController] + public class OSsController : ControllerBase + { + + private readonly SyskiDBContext context; + + public OSsController(SyskiDBContext context) + { + this.context = context; + } + + [Authorize] + [HttpGet("/system/{systemId}/operatingsystem")] + public IActionResult GetOSs(Guid systemId) + { + var applicationUserSystem = context.ApplicationUserSystems.FirstOrDefault(u => u.User.Email.Equals(((ClaimsIdentity) User.Identity).FindFirst("email").Value) && u.SystemId.Equals(systemId)); + + if (applicationUserSystem == null) + return NotFound(); + + var SystemOSs = context.SystemOSs.Where(so => so.SystemId == systemId).ToList(); + + var OSDTOs = new List(); + foreach (var item in SystemOSs) + { + OSDTOs.Add(CreateOSDTO(item)); + } + + return Ok(OSDTOs); + } + + private OSDTO CreateOSDTO(SystemOS systemOS) + { + var OperatingSystem = context.OperatingSystemModels.Find(systemOS.OperatingSystemId); + + var OSDTO = new OSDTO() + { + Id = systemOS.OperatingSystemId, + Name = OperatingSystem.Name, + Version = systemOS.Version + }; + + if (systemOS.ArchitectureId != null) + { + var architecture = context.Architectures.Find(systemOS.ArchitectureId); + OSDTO.ArchitectureName = architecture.Name; + } + + return OSDTO; + } + + } +} diff --git a/syski_api/uk.co.syski.api/Controllers/RAMsController.cs b/syski_api/uk.co.syski.api/Controllers/RAMsController.cs new file mode 100644 index 0000000..0061560 --- /dev/null +++ b/syski_api/uk.co.syski.api/Controllers/RAMsController.cs @@ -0,0 +1,104 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Syski.API.Models; +using Syski.Data; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; + +namespace Syski.API.Controllers +{ + [ApiController] + public class RAMsController : ControllerBase + { + + private readonly SyskiDBContext context; + + public RAMsController(SyskiDBContext context) + { + this.context = context; + } + + [Authorize] + [HttpGet("/system/{systemId}/ram")] + public IActionResult GetRAMs(Guid systemId) + { + var applicationUserSystem = context.ApplicationUserSystems.FirstOrDefault(u => u.User.Email.Equals(((ClaimsIdentity) User.Identity).FindFirst("email").Value) && u.SystemId.Equals(systemId)); + + if (applicationUserSystem == null) + return NotFound(); + + var lastUpdatedRAM = context.SystemRAMs.Where(sc => sc.SystemId.Equals(systemId)).OrderByDescending(i => i.LastUpdated).FirstOrDefault(); + DateTime? lastUpdated = (lastUpdatedRAM != null ? lastUpdatedRAM.LastUpdated : (DateTime?) null); + var RAMs = context.SystemRAMs.Where(sc => sc.SystemId.Equals(systemId) && sc.LastUpdated.Equals(lastUpdated)).ToList(); + + var RAMDTOs = new List(); + + foreach (var item in RAMs) + { + RAMDTOs.Add(CreateRAMDTO(item)); + } + + return Ok(RAMDTOs); + } + + [Authorize] + [HttpGet("/system/{systemId}/ram/data")] + public IActionResult GetRAMData(Guid systemId) + { + var applicationUserSystem = context.ApplicationUserSystems + .Where(u => u.User.Email == ((ClaimsIdentity)User.Identity).FindFirst("email").Value && u.SystemId == systemId).FirstOrDefault(); + + if (applicationUserSystem == null) + return NotFound(); + + var ramData = context.SystemRAMData.Where(sc => sc.SystemId == systemId).OrderByDescending(i => i.CollectionDateTime).FirstOrDefault(); + + if (ramData == null || ramData.CollectionDateTime.AddSeconds(9) < DateTime.Now) + { + return NotFound(); + } + else + { + var ramDTOs = new List(); + ramDTOs.Add(CreateRAMDataDTO(ramData)); + return Ok(ramDTOs); + } + } + + private RAMDTO CreateRAMDTO(SystemRAM systemRAM) + { + RAMModel ramModel = context.RAMModels.Find(systemRAM.RAMModelId); + + RAMDTO ramDTO = new RAMDTO() + { + Id = ramModel.Id, + MemoryBytes = ramModel.Size + }; + + if (ramModel.ModelId != null) + { + var model = context.Models.Find(ramModel.ModelId); + ramDTO.ModelName = model.Name; + if (model.ManufacturerId != null) + { + var manufacturer = context.Manufacturers.Find(model.ManufacturerId); + ramDTO.ManufacturerName = manufacturer.Name; + } + } + + return ramDTO; + } + + private RAMDataDTO CreateRAMDataDTO(SystemRAMData systemRAMData) + { + return new RAMDataDTO + { + Free = systemRAMData.Free, + CollectionDateTime = systemRAMData.CollectionDateTime + }; + } + + } +} diff --git a/syski_api/uk.co.syski.api/Controllers/StoragesController.cs b/syski_api/uk.co.syski.api/Controllers/StoragesController.cs new file mode 100644 index 0000000..e666c7a --- /dev/null +++ b/syski_api/uk.co.syski.api/Controllers/StoragesController.cs @@ -0,0 +1,114 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Syski.API.Models; +using Syski.Data; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; + +namespace Syski.API.Controllers +{ + [ApiController] + public class StoragesController : ControllerBase + { + + private readonly SyskiDBContext context; + + public StoragesController(SyskiDBContext context) + { + this.context = context; + } + + [Authorize] + [HttpGet("/system/{systemId}/storage")] + public IActionResult GetStorages(Guid systemId) + { + var applicationUserSystem = context.ApplicationUserSystems.FirstOrDefault(u => u.User.Email.Equals(((ClaimsIdentity) User.Identity).FindFirst("email").Value) && u.SystemId.Equals(systemId)); + + if (applicationUserSystem == null) + return NotFound(); + + var lastUpdatedStorage = context.SystemStorages.Where(sc => sc.SystemId.Equals(systemId)).OrderByDescending(i => i.LastUpdated).FirstOrDefault(); + DateTime? lastUpdated = (lastUpdatedStorage != null ? lastUpdatedStorage.LastUpdated : (DateTime?) null); + var Storages = context.SystemStorages.Where(sc => sc.SystemId.Equals(systemId) && sc.LastUpdated.Equals(lastUpdated)).ToList(); + + var StorageDTOs = new List(); + + foreach (var item in Storages) + { + StorageDTOs.Add(CreateStorageDTO(item)); + } + + return Ok(StorageDTOs); + } + + [Authorize] + [HttpGet("/system/{systemId}/storage/data")] + public IActionResult GetStorageData(Guid systemId) + { + var applicationUserSystem = context.ApplicationUserSystems.FirstOrDefault(u => u.User.Email.Equals(((ClaimsIdentity)User.Identity).FindFirst("email").Value) && u.SystemId.Equals(systemId)); + + if (applicationUserSystem == null) + return NotFound(); + + var storageData = context.SystemStorageData.Where(sc => sc.SystemId.Equals(systemId)).OrderByDescending(i => i.CollectionDateTime).FirstOrDefault(); + + if (storageData == null || storageData.CollectionDateTime.AddSeconds(9) < DateTime.Now) + { + return NotFound(); + } + else + { + var storageDTOs = new List(); + storageDTOs.Add(CreateStorageDataDTO(storageData)); + return Ok(storageDTOs); + } + } + + private StorageDTO CreateStorageDTO(SystemStorage systemStorage) + { + StorageModel storageModel = context.StorageModels.Find(systemStorage.StorageModelId); + + StorageDTO storageDTO = new StorageDTO() + { + Id = storageModel.Id, + MemoryBytes = storageModel.Size + }; + + if (storageModel.ModelId != null) + { + var model = context.Models.Find(storageModel.ModelId); + storageDTO.ModelName = model.Name; + if (model.ManufacturerId != null) + { + var manufacturer = context.Manufacturers.Find(model.ManufacturerId); + storageDTO.ManufacturerName = manufacturer.Name; + } + } + + if (systemStorage.StorageInterfaceId != null) + { + StorageInterfaceType StorageType = context.StorageInterfaceTypes.Find(systemStorage.StorageInterfaceId); + storageDTO.MemoryTypeName = (StorageType != null ? StorageType.Name : null); + } + + return storageDTO; + } + + private StorageDataDTO CreateStorageDataDTO(SystemStorageData systemStorageData) + { + return new StorageDataDTO + { + ByteReads = systemStorageData.ByteReads, + ByteWrites = systemStorageData.ByteWrites, + Reads = systemStorageData.Reads, + Writes = systemStorageData.Writes, + Time = systemStorageData.Time, + Transfers = systemStorageData.Transfers, + CollectionDateTime = systemStorageData.CollectionDateTime + }; + } + + } +} diff --git a/syski_api/uk.co.syski.api/Controllers/SystemsController.cs b/syski_api/uk.co.syski.api/Controllers/SystemsController.cs new file mode 100644 index 0000000..f70ed82 --- /dev/null +++ b/syski_api/uk.co.syski.api/Controllers/SystemsController.cs @@ -0,0 +1,214 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Newtonsoft.Json.Linq; +using Syski.API.Models; +using Syski.Data; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; + +namespace Syski.API.Controllers +{ + [ApiController] + public class SystemsController : ControllerBase + { + + private readonly SyskiDBContext context; + + public SystemsController(SyskiDBContext context) + { + this.context = context; + } + + [Authorize] + [HttpGet("/system/all")] + public IActionResult GetSystemsIndex() + { + var applicationUserSystems = context.ApplicationUserSystems.Where(u => u.User.Email.Equals(((ClaimsIdentity) User.Identity).FindFirst("email").Value)).ToList(); + + List systemDTOs = new List(); + foreach (var item in applicationUserSystems) + { + systemDTOs.Add(CreateSystemDTO(context.Systems.First(s => s.Id == item.SystemId))); + } + return Ok(systemDTOs); + } + + [Authorize] + [HttpGet("/system/{systemId}/ping")] + public IActionResult GetPing(Guid systemId) + { + var applicationUserSystem = context.ApplicationUserSystems.FirstOrDefault(u => u.User.Email.Equals(((ClaimsIdentity)User.Identity).FindFirst("email").Value) && u.SystemId.Equals(systemId)); + + if (applicationUserSystem == null) + return NotFound(); + + var pingData = context.SystemPingData.Where(sc => sc.SystemId.Equals(systemId)).OrderByDescending(i => i.CollectionDateTime).FirstOrDefault(); + + if (pingData == null || pingData.CollectionDateTime.AddSeconds(30) < DateTime.Now) + { + return NotFound(); + } + else + { + return Ok(new SystemPingDTO + { + ping = pingData.CollectionDateTime.Subtract(pingData.SendPingTime).TotalMilliseconds + }); + } + } + + [Authorize] + [HttpPost("/system/{systemId}/restart")] + public IActionResult RestartSystem(Guid systemId) + { + var applicationUserSystem = context.ApplicationUserSystems.FirstOrDefault(u => u.User.Email.Equals(((ClaimsIdentity)User.Identity).FindFirst("email").Value) && u.SystemId.Equals(systemId)); + + if (applicationUserSystem == null) + return NotFound(); + + context.Add(new SystemCommand + { + SystemId = systemId, + Action = "restart", + QueuedTime = DateTime.Now + }); + context.SaveChanges(); + + return Ok(); + } + + [Authorize] + [HttpPost("/system/{systemId}/shutdown")] + public IActionResult ShutdownSystem(Guid systemId) + { + var applicationUserSystem = context.ApplicationUserSystems.FirstOrDefault(u => u.User.Email.Equals(((ClaimsIdentity)User.Identity).FindFirst("email").Value) && u.SystemId.Equals(systemId)); + + if (applicationUserSystem == null) + return NotFound(); + + context.Add(new SystemCommand + { + SystemId = systemId, + Action = "shutdown", + QueuedTime = DateTime.Now + }); + context.SaveChanges(); + + return Ok(); + } + + [Authorize] + [HttpPost("/system/{systemId}/remove")] + public IActionResult RemoveSystem(Guid systemId) + { + var applicationUserSystem = context.ApplicationUserSystems.FirstOrDefault(u => u.User.Email.Equals(((ClaimsIdentity)User.Identity).FindFirst("email").Value) && u.SystemId.Equals(systemId)); + + if (applicationUserSystem == null) + return NotFound(); + + context.Remove(applicationUserSystem); + context.SaveChanges(); + + return Ok(); + } + + [Authorize] + [HttpPost("/system/{systemId}/processes/kill")] + public IActionResult KillProcess(Guid systemId, [FromBody] KillProcessDTO killProcessDTO) + { + if (!ModelState.IsValid) + { + return BadRequest(ModelState); + } + + var applicationUserSystem = context.ApplicationUserSystems.FirstOrDefault(u => u.User.Email.Equals(((ClaimsIdentity)User.Identity).FindFirst("email").Value) && u.SystemId.Equals(systemId)); + + if (applicationUserSystem == null) + return NotFound(); + + JObject properties = new JObject { { "id", killProcessDTO.Id } }; + context.Add(new SystemCommand + { + SystemId = systemId, + Action = "killprocess", + Properties = properties.ToString(), + QueuedTime = DateTime.Now + }); + context.SaveChanges(); + + return Ok(); + } + + [Authorize] + [HttpGet("/system/{systemId}/processes")] + public IActionResult GetProcesses(Guid systemId) + { + var applicationUserSystem = context.ApplicationUserSystems.FirstOrDefault(u => u.User.Email.Equals(((ClaimsIdentity) User.Identity).FindFirst("email").Value) && u.SystemId.Equals(systemId)); + + if (applicationUserSystem == null) + return NotFound(); + + var processesData = context.SystemRunningProcesses.Where(sc => sc.SystemId.Equals(systemId)).OrderByDescending(i => i.CollectionDateTime).FirstOrDefault(); + if (processesData == null || processesData.CollectionDateTime.AddSeconds(30) <= DateTime.Now) + { + return NotFound(); + } + else + { + var runningProcessesDTOs = new List(); + var processesList = context.SystemRunningProcesses.Where(sc => sc.SystemId.Equals(systemId) && sc.CollectionDateTime.Equals(processesData.CollectionDateTime)).ToList(); + foreach (var process in processesList) + { + runningProcessesDTOs.Add(new RunningProcessesDTO + { + Id = process.Id, + CollectionDateTime = process.CollectionDateTime, + KernelTime = process.KernelTime, + MemSize = process.MemSize, + Name = process.Name, + ParentId = process.ParentId, + Path = process.Path, + Threads = process.Threads, + UpTime = process.UpTime + }); + } + return Ok(runningProcessesDTOs); + } + } + + private SystemDTO CreateSystemDTO(Data.System item) + { + var systemDTO = new SystemDTO() + { + Id = item.Id, + HostName = item.HostName, + LastUpdated = item.LastUpdated + }; + + if (item.ModelId != null) + { + var model = context.Models.Find(item.ModelId); + systemDTO.ModelName = model.Name; + if (model.ManufacturerId != null) + { + var manufacturer = context.Manufacturers.Find(model.ManufacturerId); + systemDTO.ManufacturerName = manufacturer.Name; + } + } + + var systemTypes = context.SystemTypes.Where(smt => smt.SystemId == item.Id).ToList(); + var types = new List(); + foreach (var systemType in systemTypes) + { + var systemTypeName = context.SystemTypeNames.Find(systemType.TypeId); + types.Add(systemTypeName.Name); + } + systemDTO.SystemTypes = types; + + return systemDTO; + } + + } +} diff --git a/syski_api/uk.co.syski.api/Controllers/UserAuthenticationController.cs b/syski_api/uk.co.syski.api/Controllers/UserAuthenticationController.cs new file mode 100644 index 0000000..31a83d6 --- /dev/null +++ b/syski_api/uk.co.syski.api/Controllers/UserAuthenticationController.cs @@ -0,0 +1,155 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Configuration; +using Microsoft.IdentityModel.Tokens; +using Syski.API.Models; +using Syski.API.Services; +using Syski.Data; +using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; +using System.Linq; +using System.Security.Claims; +using System.Text; +using System.Threading.Tasks; + +namespace Syski.API.Controllers +{ + public class UserAuthenticationController : ControllerBase + { + + private readonly IConfiguration configuration; + private readonly UserManager userManager; + private readonly UserTokenManager tokenManager; + + public UserAuthenticationController(IConfiguration configuration, UserManager userManager, UserTokenManager tokenManager) + { + this.configuration = configuration; + this.userManager = userManager; + this.tokenManager = tokenManager; + } + + [HttpPost("/auth/user/login")] + public async Task UserLogin([FromBody] UserLoginDTO userLoginDTO) + { + IActionResult result = null; + if (!ModelState.IsValid) + { + result = BadRequest(ModelState); + } + else + { + ApplicationUser user = userManager.Users.SingleOrDefault(r => r.Email == userLoginDTO.Email); + if (user != null) + { + if (await userManager.CheckPasswordAsync(user, userLoginDTO.Password)) + { + string refreshToken = null; + AuthenticationToken token = tokenManager.CreateToken(user, ref refreshToken); + result = Ok(new UserTokenDTO() + { + Id = token.User.Id, + Email = token.User.Email, + AccessToken = GenerateJwtToken(token), + RefreshToken = refreshToken, + Expiry = token.Expires + }); + } + else + { + result = BadRequest(); + } + } + else + { + result = BadRequest(); + } + } + return result; + } + + [HttpPost("/auth/user/register")] + public async Task UserRegister([FromBody] UserRegisterDTO registerDTO) + { + IActionResult result = null; + if (!ModelState.IsValid) + { + return BadRequest(ModelState); + } + else + { + ApplicationUser newUser = new ApplicationUser + { + UserName = registerDTO.Email, + Email = registerDTO.Email + }; + if (((IdentityResult) await userManager.CreateAsync(newUser, registerDTO.Password)).Succeeded) + { + var user = userManager.Users.SingleOrDefault(r => r.Email == registerDTO.Email); + string refreshToken = null; + AuthenticationToken token = tokenManager.CreateToken(user, ref refreshToken); + result = Ok(new UserTokenDTO() + { + Id = token.User.Id, + Email = token.User.Email, + AccessToken = GenerateJwtToken(token), + RefreshToken = refreshToken, + Expiry = token.Expires + }); + } + else + { + return BadRequest(ModelState); + } + } + return result; + } + + [HttpPost("/auth/user/token/refresh")] + [Authorize("refreshtoken")] + public IActionResult UserRefreshToken([FromBody] UserRefreshTokenDTO refreshTokenDTO) + { + var claimsIdentity = User.Identity as ClaimsIdentity; + if (tokenManager.CheckValidRefreshToken(refreshTokenDTO.RefreshToken, claimsIdentity.FindFirst("jti").Value)) + { + var user = userManager.Users.SingleOrDefault(r => r.Email == claimsIdentity.FindFirst("email").Value); + string refreshToken = null; + AuthenticationToken token = tokenManager.CreateToken(user, ref refreshToken, claimsIdentity.FindFirst("jti").Value); + return Ok(new UserTokenDTO() + { + Id = token.User.Id, + Email = token.User.Email, + AccessToken = GenerateJwtToken(token), + RefreshToken = refreshToken, + Expiry = token.Expires + }); + } + return BadRequest(); + } + + private string GenerateJwtToken(AuthenticationToken Token) + { + var claims = new List + { + new Claim(JwtRegisteredClaimNames.Jti, Token.Id.ToString()), + new Claim(JwtRegisteredClaimNames.Email, Token.User.Email) + }; + + var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["Jwt:Key"])); + var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); + + var token = new JwtSecurityToken( + configuration["Jwt:Issuer"], + configuration["Jwt:Audience"], + claims, + //expires: DateTime.Now.AddDays(Convert.ToDouble(_configuration["Jwt:ExpireDays"])), + expires: Token.Expires, + notBefore: Token.NotBefore, + signingCredentials: creds + ); + + return new JwtSecurityTokenHandler().WriteToken(token); + } + + } +} diff --git a/syski_api/uk.co.syski.api/Models/BIOSDTO.cs b/syski_api/uk.co.syski.api/Models/BIOSDTO.cs new file mode 100644 index 0000000..2c22c56 --- /dev/null +++ b/syski_api/uk.co.syski.api/Models/BIOSDTO.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Syski.API.Models +{ + public class BIOSDTO + { + + public Guid Id { get; set; } + + public string ManufacturerName { get; set; } + + public string Caption { get; set; } + + public string Version { get; set; } + + public string Date { get; set; } + + } +} diff --git a/syski_api/uk.co.syski.api/Models/CPUDTO.cs b/syski_api/uk.co.syski.api/Models/CPUDTO.cs new file mode 100644 index 0000000..03db32c --- /dev/null +++ b/syski_api/uk.co.syski.api/Models/CPUDTO.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Syski.API.Models +{ + public class CPUDTO + { + + public Guid Id { get; set; } + + public string ModelName { get; set; } + + public string ManufacturerName { get; set; } + + public string ArchitectureName { get; set; } + + public double ClockSpeed { get; set; } + + public int CoreCount { get; set; } + + public int ThreadCount { get; set; } + + } +} diff --git a/syski_api/uk.co.syski.api/Models/CPUDataDTO.cs b/syski_api/uk.co.syski.api/Models/CPUDataDTO.cs new file mode 100644 index 0000000..3f82329 --- /dev/null +++ b/syski_api/uk.co.syski.api/Models/CPUDataDTO.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Syski.API.Models +{ + public class CPUDataDTO + { + + public double Load { get; set; } + + public int Processes { get; set; } + + public DateTime CollectionDateTime { get; set; } + + } +} diff --git a/syski_api/uk.co.syski.api/Models/GPUDTO.cs b/syski_api/uk.co.syski.api/Models/GPUDTO.cs new file mode 100644 index 0000000..6650a80 --- /dev/null +++ b/syski_api/uk.co.syski.api/Models/GPUDTO.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Syski.API.Models +{ + public class GPUDTO + { + + public Guid Id { get; set; } + + public string ModelName { get; set; } + + public string ManufacturerName { get; set; } + + } +} diff --git a/syski_api/uk.co.syski.api/Models/KillProcessDTO.cs b/syski_api/uk.co.syski.api/Models/KillProcessDTO.cs new file mode 100644 index 0000000..156c192 --- /dev/null +++ b/syski_api/uk.co.syski.api/Models/KillProcessDTO.cs @@ -0,0 +1,16 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Syski.API.Models +{ + public class KillProcessDTO + { + + [JsonProperty(PropertyName = "id")] + public int Id { get; set; } + + } +} diff --git a/syski_api/uk.co.syski.api/Models/MotherboardDTO.cs b/syski_api/uk.co.syski.api/Models/MotherboardDTO.cs new file mode 100644 index 0000000..699309d --- /dev/null +++ b/syski_api/uk.co.syski.api/Models/MotherboardDTO.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Syski.API.Models +{ + public class MotherboardDTO + { + + public Guid Id { get; set; } + + public string ModelName { get; set; } + + public string ManufacturerName { get; set; } + + public string Version { get; set; } + + } +} diff --git a/syski_api/uk.co.syski.api/Models/OSDTO.cs b/syski_api/uk.co.syski.api/Models/OSDTO.cs new file mode 100644 index 0000000..f379025 --- /dev/null +++ b/syski_api/uk.co.syski.api/Models/OSDTO.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Syski.API.Models +{ + public class OSDTO + { + + public Guid Id { get; set; } + + public string Name { get; set; } + + public string ArchitectureName { get; set; } + + public string Version { get; set; } + + } +} diff --git a/syski_api/uk.co.syski.api/Models/RAMDTO.cs b/syski_api/uk.co.syski.api/Models/RAMDTO.cs new file mode 100644 index 0000000..25bdd90 --- /dev/null +++ b/syski_api/uk.co.syski.api/Models/RAMDTO.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Syski.API.Models +{ + public class RAMDTO + { + + public Guid Id { get; set; } + + public string ModelName { get; set; } + + public string ManufacturerName { get; set; } + + public long MemoryBytes { get; set; } + + } +} diff --git a/syski_api/uk.co.syski.api/Models/RAMDataDTO.cs b/syski_api/uk.co.syski.api/Models/RAMDataDTO.cs new file mode 100644 index 0000000..f336a39 --- /dev/null +++ b/syski_api/uk.co.syski.api/Models/RAMDataDTO.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Syski.API.Models +{ + public class RAMDataDTO + { + + public int Free { get; set; } + + public DateTime CollectionDateTime { get; set; } + + } +} diff --git a/syski_api/uk.co.syski.api/Models/RunningProcessesDTO.cs b/syski_api/uk.co.syski.api/Models/RunningProcessesDTO.cs new file mode 100644 index 0000000..9e603a8 --- /dev/null +++ b/syski_api/uk.co.syski.api/Models/RunningProcessesDTO.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Syski.API.Models +{ + public class RunningProcessesDTO + { + + public int Id { get; set; } + + public string Name { get; set; } + + public long MemSize { get; set; } + + public long KernelTime { get; set; } + + public string Path { get; set; } + + public int Threads { get; set; } + + public long UpTime { get; set; } + + public int ParentId { get; set; } + + public DateTime CollectionDateTime { get; set; } + + } +} diff --git a/syski_api/uk.co.syski.api/Models/StorageDTO.cs b/syski_api/uk.co.syski.api/Models/StorageDTO.cs new file mode 100644 index 0000000..04c3043 --- /dev/null +++ b/syski_api/uk.co.syski.api/Models/StorageDTO.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Syski.API.Models +{ + public class StorageDTO + { + + public Guid Id { get; set; } + + public string ModelName { get; set; } + + public string ManufacturerName { get; set; } + + public string MemoryTypeName { get; set; } + + public long MemoryBytes { get; set; } + + } +} diff --git a/syski_api/uk.co.syski.api/Models/StorageDataDTO.cs b/syski_api/uk.co.syski.api/Models/StorageDataDTO.cs new file mode 100644 index 0000000..baa7034 --- /dev/null +++ b/syski_api/uk.co.syski.api/Models/StorageDataDTO.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Syski.API.Models +{ + public class StorageDataDTO + { + + public float Time { get; set; } + + public float Transfers { get; set; } + + public float Reads { get; set; } + + public float Writes { get; set; } + + public float ByteReads { get; set; } + + public float ByteWrites { get; set; } + + public DateTime CollectionDateTime { get; set; } + + } +} diff --git a/syski_api/uk.co.syski.api/Models/SystemDTO.cs b/syski_api/uk.co.syski.api/Models/SystemDTO.cs new file mode 100644 index 0000000..a6bb796 --- /dev/null +++ b/syski_api/uk.co.syski.api/Models/SystemDTO.cs @@ -0,0 +1,29 @@ +using Syski.Data; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Syski.API.Models +{ + public class SystemDTO + { + + public Guid Id { get; set; } + + public string HostName { get; set; } + + public string ModelName { get; set; } + + public string ManufacturerName { get; set; } + + public List SystemTypes { get; set; } + + public bool Online { get; set; } + + public double Ping { get; set; } + + public DateTime LastUpdated { get; set; } + + } +} diff --git a/syski_api/uk.co.syski.api/Models/SystemPingDTO.cs b/syski_api/uk.co.syski.api/Models/SystemPingDTO.cs new file mode 100644 index 0000000..625fd23 --- /dev/null +++ b/syski_api/uk.co.syski.api/Models/SystemPingDTO.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Syski.API.Models +{ + public class SystemPingDTO + { + + public double ping { get; set; } + + } +} diff --git a/syski_api/uk.co.syski.api/Models/UserAuthDTO.cs b/syski_api/uk.co.syski.api/Models/UserAuthDTO.cs new file mode 100644 index 0000000..55eb1b3 --- /dev/null +++ b/syski_api/uk.co.syski.api/Models/UserAuthDTO.cs @@ -0,0 +1,16 @@ +using System.ComponentModel.DataAnnotations; + +namespace Syski.API.Models +{ + public class UserAuthDTO + { + + [Required] + public string Email { get; set; } + + [Required] + [StringLength(100, MinimumLength = 6)] + public string Password { get; set; } + + } +} diff --git a/syski_api/uk.co.syski.api/Models/UserLoginDTO.cs b/syski_api/uk.co.syski.api/Models/UserLoginDTO.cs new file mode 100644 index 0000000..04d5e82 --- /dev/null +++ b/syski_api/uk.co.syski.api/Models/UserLoginDTO.cs @@ -0,0 +1,7 @@ +namespace Syski.API.Models +{ + public class UserLoginDTO : UserAuthDTO + { + + } +} diff --git a/syski_api/uk.co.syski.api/Models/UserRefreshTokenDTO.cs b/syski_api/uk.co.syski.api/Models/UserRefreshTokenDTO.cs new file mode 100644 index 0000000..77069e5 --- /dev/null +++ b/syski_api/uk.co.syski.api/Models/UserRefreshTokenDTO.cs @@ -0,0 +1,12 @@ +using Newtonsoft.Json; + +namespace Syski.API.Models +{ + public class UserRefreshTokenDTO + { + + [JsonProperty(PropertyName = "refresh_token")] + public string RefreshToken { get; set; } + + } +} diff --git a/syski_api/uk.co.syski.api/Models/UserRegisterDTO.cs b/syski_api/uk.co.syski.api/Models/UserRegisterDTO.cs new file mode 100644 index 0000000..020fe91 --- /dev/null +++ b/syski_api/uk.co.syski.api/Models/UserRegisterDTO.cs @@ -0,0 +1,7 @@ +namespace Syski.API.Models +{ + public class UserRegisterDTO : UserAuthDTO + { + + } +} diff --git a/syski_api/uk.co.syski.api/Models/UserTokenDTO.cs b/syski_api/uk.co.syski.api/Models/UserTokenDTO.cs new file mode 100644 index 0000000..d7bb5f7 --- /dev/null +++ b/syski_api/uk.co.syski.api/Models/UserTokenDTO.cs @@ -0,0 +1,24 @@ +using Newtonsoft.Json; +using System; + +namespace Syski.API.Models +{ + public class UserTokenDTO + { + + public string Id { get; set; } + + + public string Email { get; set; } + + [JsonProperty(PropertyName = "access_token")] + public string AccessToken { get; set; } + + [JsonProperty(PropertyName = "refresh_token")] + public string RefreshToken { get; set; } + + [JsonProperty(PropertyName = "expiry")] + public DateTime Expiry { get; set; } + + } +} diff --git a/syski_api/uk.co.syski.api/Program.cs b/syski_api/uk.co.syski.api/Program.cs new file mode 100644 index 0000000..d362740 --- /dev/null +++ b/syski_api/uk.co.syski.api/Program.cs @@ -0,0 +1,17 @@ +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; + +namespace csharp.api +{ + public class Program + { + public static void Main(string[] args) + { + CreateWebHostBuilder(args).Build().Run(); + } + + public static IWebHostBuilder CreateWebHostBuilder(string[] args) => + WebHost.CreateDefaultBuilder(args) + .UseStartup(); + } +} diff --git a/syski_api/uk.co.syski.api/Services/UserTokenManager.cs b/syski_api/uk.co.syski.api/Services/UserTokenManager.cs new file mode 100644 index 0000000..0dc3ef8 --- /dev/null +++ b/syski_api/uk.co.syski.api/Services/UserTokenManager.cs @@ -0,0 +1,91 @@ +using Microsoft.Extensions.Configuration; +using Syski.Data; +using System; +using System.Linq; +using System.Security.Cryptography; + +namespace Syski.API.Services +{ + public class UserTokenManager + { + + private readonly SyskiDBContext context; + private readonly IConfiguration configuration; + + public UserTokenManager(SyskiDBContext context, IConfiguration configuration) + { + this.context = context; + this.configuration = configuration; + } + + + public AuthenticationToken CreateToken(ApplicationUser user, ref string refreshToken) + { + AuthenticationToken token = GenerateToken(user, ref refreshToken); + context.Add(token); + context.SaveChanges(); + return token; + } + + public AuthenticationToken CreateToken(ApplicationUser user, ref string refreshToken, string previousTokenId) + { + AuthenticationToken token = GenerateToken(user, ref refreshToken); + var previousToken = context.AuthenticationTokens.FirstOrDefault(t => t.Id == new Guid(previousTokenId)); + context.Add(token); + context.SaveChanges(); + if (previousToken != null) + { + previousToken.Active = false; + SetPreviousToken(ref token, ref previousToken); + context.SaveChanges(); + } + return token; + } + + public bool CheckValidRefreshToken(string refreshToken, string tokenId) + { + return context.AuthenticationTokens.Any(t => t.Id == new Guid(tokenId) && t.Active && t.RefreshToken == refreshToken); + } + + private void SetPreviousToken(ref AuthenticationToken nextToken, ref AuthenticationToken previousToken) + { + previousToken.NextTokenId = nextToken.Id; + nextToken.PreviousTokenId = previousToken.Id; + } + + private AuthenticationToken GenerateToken(ApplicationUser user, ref string refreshToken) + { + AuthenticationToken result = null; + if (refreshToken == null) + { + refreshToken = GenerateRefreshToken(); + } + result = new AuthenticationToken() + { + Id = new Guid(), + User = user, + UserId = user.Id, + TokenType = "Bearer", + Issuer = configuration["Jwt:Issuer"], + Audience = configuration["Jwt:Audience"], + Subject = user.Email, + Expires = DateTime.Now.AddMinutes(Convert.ToDouble(configuration["Jwt:ExpireInMinutes"])), + NotBefore = DateTime.Now, + RefreshToken = refreshToken, + Active = true + }; + return (result); + } + + private string GenerateRefreshToken() + { + var randomNumber = new byte[128]; + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(randomNumber); + return Convert.ToBase64String(randomNumber); + } + } + + } +} diff --git a/syski_api/uk.co.syski.api/Startup.cs b/syski_api/uk.co.syski.api/Startup.cs new file mode 100644 index 0000000..176be02 --- /dev/null +++ b/syski_api/uk.co.syski.api/Startup.cs @@ -0,0 +1,134 @@ +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.HttpOverrides; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.IdentityModel.Tokens; +using Syski.API.Services; +using Syski.Data; +using System; +using System.IdentityModel.Tokens.Jwt; +using System.Text; + +namespace csharp.api +{ + public class Startup + { + + public IConfiguration Configuration { get; } + + public Startup(IConfiguration configuration) + { + Configuration = configuration; + } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); + + // Make all the urls lowercase as this is good web practice + services.AddRouting(options => options.LowercaseUrls = true); + + // Load the connection string from the settings file and use it for storing data + services.AddDbContext(options => + options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")) + ); + + // Add Identity to the application + services.AddDefaultIdentity() + .AddEntityFrameworkStores(); + + // Java Web Tokens Authentication + JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); + services.AddAuthentication(options => + { + options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; + options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; + + }) + .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, cfg => + { + cfg.RequireHttpsMetadata = true; + cfg.SaveToken = true; + cfg.TokenValidationParameters = new TokenValidationParameters + { + ValidateIssuerSigningKey = true, + IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:Key"])), + ValidateIssuer = true, + ValidIssuer = Configuration["Jwt:Issuer"], + ValidateAudience = true, + ValidAudience = Configuration["Jwt:Audience"], + ValidateLifetime = true, + ClockSkew = TimeSpan.Zero + //ClockSkew = TimeSpan.FromMinutes(5) + }; + }) + .AddJwtBearer("refresh", cfg => + { + cfg.RequireHttpsMetadata = true; + cfg.SaveToken = true; + cfg.TokenValidationParameters = new TokenValidationParameters + { + ValidateIssuerSigningKey = true, + IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:Key"])), + ValidateIssuer = true, + ValidIssuer = Configuration["Jwt:Issuer"], + ValidateAudience = true, + ValidAudience = Configuration["Jwt:Audience"], + ValidateLifetime = false, + ClockSkew = TimeSpan.Zero + //ClockSkew = TimeSpan.FromMinutes(5) + }; + }); + + services.AddAuthorization(options => + { + options.AddPolicy("refreshtoken", new AuthorizationPolicyBuilder().RequireAuthenticatedUser().AddAuthenticationSchemes("refresh").Build()); + }); + + services.AddTransient(); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IHostingEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + app.UseDatabaseErrorPage(); + } + else + { + app.UseHsts(); + } + + try + { + if (Convert.ToBoolean(Configuration["ReverseProxy"])) + { + app.UseForwardedHeaders(new ForwardedHeadersOptions + { + ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto + }); + } + } + catch + { + // Error parsing config, do nothing assume not behind a reverse proxy + } + + app.UseHttpsRedirection(); + + app.UseAuthentication(); + + app.UseMvc(); + } + + } +} diff --git a/syski_api/uk.co.syski.api/Syski.API.csproj b/syski_api/uk.co.syski.api/Syski.API.csproj new file mode 100644 index 0000000..25fd2cd --- /dev/null +++ b/syski_api/uk.co.syski.api/Syski.API.csproj @@ -0,0 +1,21 @@ + + + + netcoreapp2.1 + + + + + + + + + + + + + + + + + diff --git a/syski_api/uk.co.syski.api/appsettings.Development.json b/syski_api/uk.co.syski.api/appsettings.Development.json new file mode 100644 index 0000000..e203e94 --- /dev/null +++ b/syski_api/uk.co.syski.api/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + } +} diff --git a/syski_api/uk.co.syski.api/appsettings.json b/syski_api/uk.co.syski.api/appsettings.json new file mode 100644 index 0000000..9dc19ca --- /dev/null +++ b/syski_api/uk.co.syski.api/appsettings.json @@ -0,0 +1,18 @@ +{ + "ReverseProxy": true, + "JWT": { + "Key": "AVerySimpleKeyThatNeedsChanging", + "ExpireInMinutes": 30, + "Issuer": "api.syski.co.uk", + "Audience": "api.syski.co.uk" + }, + "ConnectionStrings": { + "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=SyskiAPI;Trusted_Connection=True;MultipleActiveResultSets=true" + }, + "Logging": { + "LogLevel": { + "Default": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/syski_api/uk.co.syski.data/ApplicationUser.cs b/syski_api/uk.co.syski.data/ApplicationUser.cs new file mode 100644 index 0000000..da72630 --- /dev/null +++ b/syski_api/uk.co.syski.data/ApplicationUser.cs @@ -0,0 +1,14 @@ +using Microsoft.AspNetCore.Identity; +using System.Collections.Generic; + +namespace Syski.Data +{ + public class ApplicationUser : IdentityUser + { + + public IEnumerable Systems { get; set; } + + public IEnumerable AuthenticationTokens { get; set; } + + } +} diff --git a/syski_api/uk.co.syski.data/ApplicationUserSystemCategory.cs b/syski_api/uk.co.syski.data/ApplicationUserSystemCategory.cs new file mode 100644 index 0000000..2ccbf0c --- /dev/null +++ b/syski_api/uk.co.syski.data/ApplicationUserSystemCategory.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Syski.Data +{ + public class ApplicationUserSystemCategory + { + + public Guid Id { get; set; } + + public string Name { get; set; } + + public IEnumerable Systems { get; set; } + + } +} diff --git a/syski_api/uk.co.syski.data/ApplicationUserSystems.cs b/syski_api/uk.co.syski.data/ApplicationUserSystems.cs new file mode 100644 index 0000000..4b55147 --- /dev/null +++ b/syski_api/uk.co.syski.data/ApplicationUserSystems.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Syski.Data +{ + public class ApplicationUserSystems + { + + public string UserId { get; set; } + + public ApplicationUser User { get; set; } + + public Guid SystemId { get; set; } + + public System System { get; set; } + + public Guid? CategoryId { get; set; } + + public ApplicationUserSystemCategory Category { get; set; } + + } +} diff --git a/syski_api/uk.co.syski.data/Architecture.cs b/syski_api/uk.co.syski.data/Architecture.cs new file mode 100644 index 0000000..e573e2c --- /dev/null +++ b/syski_api/uk.co.syski.data/Architecture.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; + +namespace Syski.Data +{ + public class Architecture + { + + public Guid Id { get; set; } + + public string Name { get; set; } + + public IEnumerable CPUModels { get; set; } + + public IEnumerable SystemOSs { get; set; } + + } +} diff --git a/syski_api/uk.co.syski.data/AuthenticationToken.cs b/syski_api/uk.co.syski.data/AuthenticationToken.cs new file mode 100644 index 0000000..066f9bc --- /dev/null +++ b/syski_api/uk.co.syski.data/AuthenticationToken.cs @@ -0,0 +1,35 @@ +using System; + +namespace Syski.Data +{ + public class AuthenticationToken + { + + public Guid Id { get; set; } + + public string UserId { get; set; } + + public ApplicationUser User { get; set; } + + public string TokenType { get; set; } + + public string Issuer { get; set; } + + public string Audience { get; set; } + + public string Subject { get; set; } + + public DateTime Expires { get; set; } + + public DateTime NotBefore { get; set; } + + public string RefreshToken { get; set; } + + public bool Active { get; set; } + + public Guid? NextTokenId { get; set; } + + public Guid? PreviousTokenId { get; set; } + + } +} diff --git a/syski_api/uk.co.syski.data/BIOSModel.cs b/syski_api/uk.co.syski.data/BIOSModel.cs new file mode 100644 index 0000000..9bf5029 --- /dev/null +++ b/syski_api/uk.co.syski.data/BIOSModel.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Syski.Data +{ + public class BIOSModel + { + + public Guid Id { get; set; } + + public Guid? ManufacturerId { get; set; } + + public Manufacturer Manufacturer { get; set; } + + public IEnumerable SystemBIOSs { get; set; } + + } +} diff --git a/syski_api/uk.co.syski.data/CPUModel.cs b/syski_api/uk.co.syski.data/CPUModel.cs new file mode 100644 index 0000000..f23688c --- /dev/null +++ b/syski_api/uk.co.syski.data/CPUModel.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Syski.Data +{ + public class CPUModel + { + + public Guid Id { get; set; } + + public Guid? ModelId { get; set; } + + public Model Model { get; set; } + + public Guid? ArchitectureId { get; set; } + + public Architecture Architecture { get; set; } + + public IEnumerable SystemCPUs { get; set; } + + } +} diff --git a/syski_api/uk.co.syski.data/GPUModel.cs b/syski_api/uk.co.syski.data/GPUModel.cs new file mode 100644 index 0000000..7229e5f --- /dev/null +++ b/syski_api/uk.co.syski.data/GPUModel.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Syski.Data +{ + public class GPUModel + { + + public Guid Id { get; set; } + + public Guid? ModelId { get; set; } + + public Model Model { get; set; } + + public IEnumerable SystemGPUs { get; set; } + + } +} diff --git a/syski_api/uk.co.syski.data/Manufacturer.cs b/syski_api/uk.co.syski.data/Manufacturer.cs new file mode 100644 index 0000000..5f9a786 --- /dev/null +++ b/syski_api/uk.co.syski.data/Manufacturer.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Syski.Data +{ + public class Manufacturer + { + + public Guid Id { get; set; } + + public string Name { get; set; } + + public IEnumerable Models { get; set; } + + public IEnumerable BIOSManufacturer { get; set; } + + } +} diff --git a/syski_api/uk.co.syski.data/Migrations/20190428102026_Initial.Designer.cs b/syski_api/uk.co.syski.data/Migrations/20190428102026_Initial.Designer.cs new file mode 100644 index 0000000..cd6557a --- /dev/null +++ b/syski_api/uk.co.syski.data/Migrations/20190428102026_Initial.Designer.cs @@ -0,0 +1,1015 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Syski.Data; + +namespace Syski.Data.Migrations +{ + [DbContext(typeof(SyskiDBContext))] + [Migration("20190428102026_Initial")] + partial class Initial + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.1.8-servicing-32085") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Name") + .HasMaxLength(256); + + b.Property("NormalizedName") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("RoleId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasMaxLength(128); + + b.Property("ProviderKey") + .HasMaxLength(128); + + b.Property("ProviderDisplayName"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId"); + + b.Property("RoleId"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId"); + + b.Property("LoginProvider") + .HasMaxLength(128); + + b.Property("Name") + .HasMaxLength(128); + + b.Property("Value"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Syski.Data.ApplicationUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AccessFailedCount"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Email") + .HasMaxLength(256); + + b.Property("EmailConfirmed"); + + b.Property("LockoutEnabled"); + + b.Property("LockoutEnd"); + + b.Property("NormalizedEmail") + .HasMaxLength(256); + + b.Property("NormalizedUserName") + .HasMaxLength(256); + + b.Property("PasswordHash"); + + b.Property("PhoneNumber"); + + b.Property("PhoneNumberConfirmed"); + + b.Property("SecurityStamp"); + + b.Property("TwoFactorEnabled"); + + b.Property("UserName") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Syski.Data.ApplicationUserSystemCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Name"); + + b.HasKey("Id"); + + b.ToTable("ApplicationUserSystemCategory"); + }); + + modelBuilder.Entity("Syski.Data.ApplicationUserSystems", b => + { + b.Property("UserId"); + + b.Property("SystemId"); + + b.Property("CategoryId"); + + b.HasKey("UserId", "SystemId"); + + b.HasIndex("CategoryId"); + + b.HasIndex("SystemId"); + + b.ToTable("ApplicationUserSystems"); + }); + + modelBuilder.Entity("Syski.Data.Architecture", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Name"); + + b.HasKey("Id"); + + b.ToTable("Architectures"); + }); + + modelBuilder.Entity("Syski.Data.AuthenticationToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("Audience"); + + b.Property("Expires"); + + b.Property("Issuer"); + + b.Property("NextTokenId"); + + b.Property("NotBefore"); + + b.Property("PreviousTokenId"); + + b.Property("RefreshToken"); + + b.Property("Subject"); + + b.Property("TokenType"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AuthenticationTokens"); + }); + + modelBuilder.Entity("Syski.Data.BIOSModel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ManufacturerId"); + + b.HasKey("Id"); + + b.HasIndex("ManufacturerId"); + + b.ToTable("BIOSModels"); + }); + + modelBuilder.Entity("Syski.Data.CPUModel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ArchitectureId"); + + b.Property("ModelId"); + + b.HasKey("Id"); + + b.HasIndex("ArchitectureId"); + + b.HasIndex("ModelId") + .IsUnique() + .HasFilter("[ModelId] IS NOT NULL"); + + b.ToTable("CPUModels"); + }); + + modelBuilder.Entity("Syski.Data.GPUModel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ModelId"); + + b.HasKey("Id"); + + b.HasIndex("ModelId") + .IsUnique() + .HasFilter("[ModelId] IS NOT NULL"); + + b.ToTable("GPUModels"); + }); + + modelBuilder.Entity("Syski.Data.Manufacturer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Name"); + + b.HasKey("Id"); + + b.ToTable("Manufacturers"); + }); + + modelBuilder.Entity("Syski.Data.Model", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ManufacturerId"); + + b.Property("Name"); + + b.HasKey("Id"); + + b.HasIndex("ManufacturerId"); + + b.ToTable("Models"); + }); + + modelBuilder.Entity("Syski.Data.MotherboardModel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ModelId"); + + b.Property("Version"); + + b.HasKey("Id"); + + b.HasIndex("ModelId") + .IsUnique() + .HasFilter("[ModelId] IS NOT NULL"); + + b.ToTable("MotherboardModels"); + }); + + modelBuilder.Entity("Syski.Data.OperatingSystemModel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Name"); + + b.HasKey("Id"); + + b.ToTable("OperatingSystemModels"); + }); + + modelBuilder.Entity("Syski.Data.RAMModel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ModelId"); + + b.Property("Size"); + + b.HasKey("Id"); + + b.HasIndex("ModelId") + .IsUnique() + .HasFilter("[ModelId] IS NOT NULL"); + + b.ToTable("RAMModels"); + }); + + modelBuilder.Entity("Syski.Data.StorageInterfaceType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Name"); + + b.HasKey("Id"); + + b.ToTable("StorageInterfaceTypes"); + }); + + modelBuilder.Entity("Syski.Data.StorageModel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ModelId"); + + b.Property("Size"); + + b.HasKey("Id"); + + b.HasIndex("ModelId") + .IsUnique() + .HasFilter("[ModelId] IS NOT NULL"); + + b.ToTable("StorageModels"); + }); + + modelBuilder.Entity("Syski.Data.System", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("HostName"); + + b.Property("LastUpdated"); + + b.Property("ModelId"); + + b.Property("Secret"); + + b.HasKey("Id"); + + b.HasIndex("ModelId"); + + b.ToTable("Systems"); + }); + + modelBuilder.Entity("Syski.Data.SystemBIOS", b => + { + b.Property("SystemId"); + + b.Property("BIOSModelId"); + + b.Property("Caption"); + + b.Property("Date"); + + b.Property("LastUpdated"); + + b.Property("Version"); + + b.HasKey("SystemId"); + + b.HasIndex("BIOSModelId"); + + b.ToTable("SystemBIOSs"); + }); + + modelBuilder.Entity("Syski.Data.SystemCommand", b => + { + b.Property("SystemId"); + + b.Property("QueuedTime"); + + b.Property("Action"); + + b.Property("ExecutedTime"); + + b.Property("Properties"); + + b.HasKey("SystemId", "QueuedTime"); + + b.ToTable("SystemCommands"); + }); + + modelBuilder.Entity("Syski.Data.SystemCPU", b => + { + b.Property("SystemId"); + + b.Property("CPUModelID"); + + b.Property("Slot"); + + b.Property("ClockSpeed"); + + b.Property("CoreCount"); + + b.Property("LastUpdated"); + + b.Property("ThreadCount"); + + b.HasKey("SystemId", "CPUModelID", "Slot"); + + b.HasIndex("CPUModelID"); + + b.ToTable("SystemCPUs"); + }); + + modelBuilder.Entity("Syski.Data.SystemCPUData", b => + { + b.Property("SystemId"); + + b.Property("CollectionDateTime"); + + b.Property("Load"); + + b.Property("Processes"); + + b.HasKey("SystemId", "CollectionDateTime"); + + b.ToTable("SystemCPUsData"); + }); + + modelBuilder.Entity("Syski.Data.SystemGPU", b => + { + b.Property("SystemId"); + + b.Property("GPUModelId"); + + b.Property("Slot"); + + b.Property("LastUpdated"); + + b.HasKey("SystemId", "GPUModelId", "Slot"); + + b.HasIndex("GPUModelId"); + + b.ToTable("SystemGPUs"); + }); + + modelBuilder.Entity("Syski.Data.SystemMotherboard", b => + { + b.Property("SystemId"); + + b.Property("LastUpdated"); + + b.Property("MotherboardModelId"); + + b.HasKey("SystemId"); + + b.HasIndex("MotherboardModelId"); + + b.ToTable("SystemMotherboards"); + }); + + modelBuilder.Entity("Syski.Data.SystemOS", b => + { + b.Property("SystemId"); + + b.Property("OperatingSystemId"); + + b.Property("ArchitectureId"); + + b.Property("LastUpdated"); + + b.Property("Version"); + + b.HasKey("SystemId", "OperatingSystemId"); + + b.HasIndex("ArchitectureId"); + + b.HasIndex("OperatingSystemId"); + + b.ToTable("SystemOSs"); + }); + + modelBuilder.Entity("Syski.Data.SystemPingData", b => + { + b.Property("SystemId"); + + b.Property("SendPingTime"); + + b.Property("CollectionDateTime"); + + b.HasKey("SystemId", "SendPingTime"); + + b.ToTable("SystemPingData"); + }); + + modelBuilder.Entity("Syski.Data.SystemRAM", b => + { + b.Property("SystemId"); + + b.Property("RAMModelId"); + + b.Property("Slot"); + + b.Property("LastUpdated"); + + b.Property("Speed"); + + b.HasKey("SystemId", "RAMModelId", "Slot"); + + b.HasIndex("RAMModelId"); + + b.ToTable("SystemRAMs"); + }); + + modelBuilder.Entity("Syski.Data.SystemRAMData", b => + { + b.Property("SystemId"); + + b.Property("CollectionDateTime"); + + b.Property("Free"); + + b.HasKey("SystemId", "CollectionDateTime"); + + b.ToTable("SystemRAMData"); + }); + + modelBuilder.Entity("Syski.Data.SystemRunningProcesses", b => + { + b.Property("SystemId"); + + b.Property("Id"); + + b.Property("CollectionDateTime"); + + b.Property("KernelTime"); + + b.Property("MemSize"); + + b.Property("Name"); + + b.Property("ParentId"); + + b.Property("Path"); + + b.Property("Threads"); + + b.Property("UpTime"); + + b.HasKey("SystemId", "Id", "CollectionDateTime"); + + b.ToTable("SystemRunningProcesses"); + }); + + modelBuilder.Entity("Syski.Data.SystemStorage", b => + { + b.Property("SystemId"); + + b.Property("StorageModelId"); + + b.Property("Slot"); + + b.Property("LastUpdated"); + + b.Property("StorageInterfaceId"); + + b.HasKey("SystemId", "StorageModelId", "Slot"); + + b.HasIndex("StorageInterfaceId"); + + b.HasIndex("StorageModelId"); + + b.ToTable("SystemStorages"); + }); + + modelBuilder.Entity("Syski.Data.SystemStorageData", b => + { + b.Property("SystemId"); + + b.Property("CollectionDateTime"); + + b.Property("ByteReads"); + + b.Property("ByteWrites"); + + b.Property("Reads"); + + b.Property("Time"); + + b.Property("Transfers"); + + b.Property("Writes"); + + b.HasKey("SystemId", "CollectionDateTime"); + + b.ToTable("SystemStorageData"); + }); + + modelBuilder.Entity("Syski.Data.SystemType", b => + { + b.Property("SystemId"); + + b.Property("TypeId"); + + b.HasKey("SystemId", "TypeId"); + + b.HasIndex("TypeId"); + + b.ToTable("SystemTypes"); + }); + + modelBuilder.Entity("Syski.Data.SystemTypeName", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Name"); + + b.HasKey("Id"); + + b.ToTable("SystemTypeNames"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Syski.Data.ApplicationUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Syski.Data.ApplicationUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Syski.Data.ApplicationUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Syski.Data.ApplicationUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Syski.Data.ApplicationUserSystems", b => + { + b.HasOne("Syski.Data.ApplicationUserSystemCategory", "Category") + .WithMany("Systems") + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Syski.Data.System", "System") + .WithMany("Users") + .HasForeignKey("SystemId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Syski.Data.ApplicationUser", "User") + .WithMany("Systems") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict); + }); + + modelBuilder.Entity("Syski.Data.AuthenticationToken", b => + { + b.HasOne("Syski.Data.ApplicationUser", "User") + .WithMany("AuthenticationTokens") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict); + }); + + modelBuilder.Entity("Syski.Data.BIOSModel", b => + { + b.HasOne("Syski.Data.Manufacturer", "Manufacturer") + .WithMany("BIOSManufacturer") + .HasForeignKey("ManufacturerId") + .OnDelete(DeleteBehavior.Restrict); + }); + + modelBuilder.Entity("Syski.Data.CPUModel", b => + { + b.HasOne("Syski.Data.Architecture", "Architecture") + .WithMany("CPUModels") + .HasForeignKey("ArchitectureId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Syski.Data.Model", "Model") + .WithOne("CPUModel") + .HasForeignKey("Syski.Data.CPUModel", "ModelId") + .OnDelete(DeleteBehavior.Restrict); + }); + + modelBuilder.Entity("Syski.Data.GPUModel", b => + { + b.HasOne("Syski.Data.Model", "Model") + .WithOne("GPUModel") + .HasForeignKey("Syski.Data.GPUModel", "ModelId") + .OnDelete(DeleteBehavior.Restrict); + }); + + modelBuilder.Entity("Syski.Data.Model", b => + { + b.HasOne("Syski.Data.Manufacturer", "Manufacturer") + .WithMany("Models") + .HasForeignKey("ManufacturerId") + .OnDelete(DeleteBehavior.Restrict); + }); + + modelBuilder.Entity("Syski.Data.MotherboardModel", b => + { + b.HasOne("Syski.Data.Model", "Model") + .WithOne("MotherboardModel") + .HasForeignKey("Syski.Data.MotherboardModel", "ModelId") + .OnDelete(DeleteBehavior.Restrict); + }); + + modelBuilder.Entity("Syski.Data.RAMModel", b => + { + b.HasOne("Syski.Data.Model", "Model") + .WithOne("RAMModel") + .HasForeignKey("Syski.Data.RAMModel", "ModelId") + .OnDelete(DeleteBehavior.Restrict); + }); + + modelBuilder.Entity("Syski.Data.StorageModel", b => + { + b.HasOne("Syski.Data.Model", "Model") + .WithOne("StorageModel") + .HasForeignKey("Syski.Data.StorageModel", "ModelId") + .OnDelete(DeleteBehavior.Restrict); + }); + + modelBuilder.Entity("Syski.Data.System", b => + { + b.HasOne("Syski.Data.Model", "Model") + .WithMany("Systems") + .HasForeignKey("ModelId") + .OnDelete(DeleteBehavior.Restrict); + }); + + modelBuilder.Entity("Syski.Data.SystemBIOS", b => + { + b.HasOne("Syski.Data.BIOSModel", "BIOSModel") + .WithMany("SystemBIOSs") + .HasForeignKey("BIOSModelId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Syski.Data.System", "System") + .WithOne("SystemBIOS") + .HasForeignKey("Syski.Data.SystemBIOS", "SystemId") + .OnDelete(DeleteBehavior.Restrict); + }); + + modelBuilder.Entity("Syski.Data.SystemCommand", b => + { + b.HasOne("Syski.Data.System", "System") + .WithMany("SystemCommands") + .HasForeignKey("SystemId") + .OnDelete(DeleteBehavior.Restrict); + }); + + modelBuilder.Entity("Syski.Data.SystemCPU", b => + { + b.HasOne("Syski.Data.CPUModel", "CPUModel") + .WithMany("SystemCPUs") + .HasForeignKey("CPUModelID") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Syski.Data.System", "System") + .WithMany("SystemCPUs") + .HasForeignKey("SystemId") + .OnDelete(DeleteBehavior.Restrict); + }); + + modelBuilder.Entity("Syski.Data.SystemCPUData", b => + { + b.HasOne("Syski.Data.System", "System") + .WithMany("SystemCPUData") + .HasForeignKey("SystemId") + .OnDelete(DeleteBehavior.Restrict); + }); + + modelBuilder.Entity("Syski.Data.SystemGPU", b => + { + b.HasOne("Syski.Data.GPUModel", "GPUModel") + .WithMany("SystemGPUs") + .HasForeignKey("GPUModelId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Syski.Data.System", "System") + .WithMany("SystemGPUs") + .HasForeignKey("SystemId") + .OnDelete(DeleteBehavior.Restrict); + }); + + modelBuilder.Entity("Syski.Data.SystemMotherboard", b => + { + b.HasOne("Syski.Data.MotherboardModel", "MotherboardModel") + .WithMany("SystemMotherboards") + .HasForeignKey("MotherboardModelId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Syski.Data.System", "System") + .WithOne("SystemMotherboard") + .HasForeignKey("Syski.Data.SystemMotherboard", "SystemId") + .OnDelete(DeleteBehavior.Restrict); + }); + + modelBuilder.Entity("Syski.Data.SystemOS", b => + { + b.HasOne("Syski.Data.Architecture", "Architecture") + .WithMany("SystemOSs") + .HasForeignKey("ArchitectureId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Syski.Data.OperatingSystemModel", "OperatingSystem") + .WithMany("SystemOSs") + .HasForeignKey("OperatingSystemId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Syski.Data.System", "System") + .WithMany("SystemOSs") + .HasForeignKey("SystemId") + .OnDelete(DeleteBehavior.Restrict); + }); + + modelBuilder.Entity("Syski.Data.SystemPingData", b => + { + b.HasOne("Syski.Data.System", "System") + .WithMany("SystemPingData") + .HasForeignKey("SystemId") + .OnDelete(DeleteBehavior.Restrict); + }); + + modelBuilder.Entity("Syski.Data.SystemRAM", b => + { + b.HasOne("Syski.Data.RAMModel", "RAMModel") + .WithMany("SystemRAMs") + .HasForeignKey("RAMModelId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Syski.Data.System", "System") + .WithMany("SystemRAMs") + .HasForeignKey("SystemId") + .OnDelete(DeleteBehavior.Restrict); + }); + + modelBuilder.Entity("Syski.Data.SystemRAMData", b => + { + b.HasOne("Syski.Data.System", "System") + .WithMany("SystemRAMData") + .HasForeignKey("SystemId") + .OnDelete(DeleteBehavior.Restrict); + }); + + modelBuilder.Entity("Syski.Data.SystemRunningProcesses", b => + { + b.HasOne("Syski.Data.System", "System") + .WithMany("SystemRunningProcesses") + .HasForeignKey("SystemId") + .OnDelete(DeleteBehavior.Restrict); + }); + + modelBuilder.Entity("Syski.Data.SystemStorage", b => + { + b.HasOne("Syski.Data.StorageInterfaceType", "StorageInterface") + .WithMany("SystemStorages") + .HasForeignKey("StorageInterfaceId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Syski.Data.StorageModel", "StorageModel") + .WithMany("SystemStorages") + .HasForeignKey("StorageModelId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Syski.Data.System", "System") + .WithMany("SystemStorages") + .HasForeignKey("SystemId") + .OnDelete(DeleteBehavior.Restrict); + }); + + modelBuilder.Entity("Syski.Data.SystemStorageData", b => + { + b.HasOne("Syski.Data.System", "System") + .WithMany("SystemStorageData") + .HasForeignKey("SystemId") + .OnDelete(DeleteBehavior.Restrict); + }); + + modelBuilder.Entity("Syski.Data.SystemType", b => + { + b.HasOne("Syski.Data.System", "System") + .WithMany("SystemTypes") + .HasForeignKey("SystemId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Syski.Data.SystemTypeName", "Type") + .WithMany("SystemTypes") + .HasForeignKey("TypeId") + .OnDelete(DeleteBehavior.Restrict); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/syski_api/uk.co.syski.data/Migrations/20190428102026_Initial.cs b/syski_api/uk.co.syski.data/Migrations/20190428102026_Initial.cs new file mode 100644 index 0000000..ffaf8de --- /dev/null +++ b/syski_api/uk.co.syski.data/Migrations/20190428102026_Initial.cs @@ -0,0 +1,1073 @@ +using System; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Syski.Data.Migrations +{ + public partial class Initial : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "ApplicationUserSystemCategory", + columns: table => new + { + Id = table.Column(nullable: false), + Name = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_ApplicationUserSystemCategory", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Architectures", + columns: table => new + { + Id = table.Column(nullable: false), + Name = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Architectures", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetRoles", + columns: table => new + { + Id = table.Column(nullable: false), + Name = table.Column(maxLength: 256, nullable: true), + NormalizedName = table.Column(maxLength: 256, nullable: true), + ConcurrencyStamp = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetUsers", + columns: table => new + { + Id = table.Column(nullable: false), + UserName = table.Column(maxLength: 256, nullable: true), + NormalizedUserName = table.Column(maxLength: 256, nullable: true), + Email = table.Column(maxLength: 256, nullable: true), + NormalizedEmail = table.Column(maxLength: 256, nullable: true), + EmailConfirmed = table.Column(nullable: false), + PasswordHash = table.Column(nullable: true), + SecurityStamp = table.Column(nullable: true), + ConcurrencyStamp = table.Column(nullable: true), + PhoneNumber = table.Column(nullable: true), + PhoneNumberConfirmed = table.Column(nullable: false), + TwoFactorEnabled = table.Column(nullable: false), + LockoutEnd = table.Column(nullable: true), + LockoutEnabled = table.Column(nullable: false), + AccessFailedCount = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUsers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Manufacturers", + columns: table => new + { + Id = table.Column(nullable: false), + Name = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Manufacturers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "OperatingSystemModels", + columns: table => new + { + Id = table.Column(nullable: false), + Name = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_OperatingSystemModels", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "StorageInterfaceTypes", + columns: table => new + { + Id = table.Column(nullable: false), + Name = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_StorageInterfaceTypes", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "SystemTypeNames", + columns: table => new + { + Id = table.Column(nullable: false), + Name = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_SystemTypeNames", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetRoleClaims", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + RoleId = table.Column(nullable: false), + ClaimType = table.Column(nullable: true), + ClaimValue = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserClaims", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + UserId = table.Column(nullable: false), + ClaimType = table.Column(nullable: true), + ClaimValue = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetUserClaims_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserLogins", + columns: table => new + { + LoginProvider = table.Column(maxLength: 128, nullable: false), + ProviderKey = table.Column(maxLength: 128, nullable: false), + ProviderDisplayName = table.Column(nullable: true), + UserId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); + table.ForeignKey( + name: "FK_AspNetUserLogins_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserRoles", + columns: table => new + { + UserId = table.Column(nullable: false), + RoleId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserTokens", + columns: table => new + { + UserId = table.Column(nullable: false), + LoginProvider = table.Column(maxLength: 128, nullable: false), + Name = table.Column(maxLength: 128, nullable: false), + Value = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); + table.ForeignKey( + name: "FK_AspNetUserTokens_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AuthenticationTokens", + columns: table => new + { + Id = table.Column(nullable: false), + UserId = table.Column(nullable: false), + TokenType = table.Column(nullable: true), + Issuer = table.Column(nullable: true), + Audience = table.Column(nullable: true), + Subject = table.Column(nullable: true), + Expires = table.Column(nullable: false), + NotBefore = table.Column(nullable: false), + RefreshToken = table.Column(nullable: true), + Active = table.Column(nullable: false), + NextTokenId = table.Column(nullable: true), + PreviousTokenId = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AuthenticationTokens", x => x.Id); + table.ForeignKey( + name: "FK_AuthenticationTokens_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "BIOSModels", + columns: table => new + { + Id = table.Column(nullable: false), + ManufacturerId = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_BIOSModels", x => x.Id); + table.ForeignKey( + name: "FK_BIOSModels_Manufacturers_ManufacturerId", + column: x => x.ManufacturerId, + principalTable: "Manufacturers", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "Models", + columns: table => new + { + Id = table.Column(nullable: false), + Name = table.Column(nullable: true), + ManufacturerId = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Models", x => x.Id); + table.ForeignKey( + name: "FK_Models_Manufacturers_ManufacturerId", + column: x => x.ManufacturerId, + principalTable: "Manufacturers", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "CPUModels", + columns: table => new + { + Id = table.Column(nullable: false), + ModelId = table.Column(nullable: true), + ArchitectureId = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_CPUModels", x => x.Id); + table.ForeignKey( + name: "FK_CPUModels_Architectures_ArchitectureId", + column: x => x.ArchitectureId, + principalTable: "Architectures", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_CPUModels_Models_ModelId", + column: x => x.ModelId, + principalTable: "Models", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "GPUModels", + columns: table => new + { + Id = table.Column(nullable: false), + ModelId = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_GPUModels", x => x.Id); + table.ForeignKey( + name: "FK_GPUModels_Models_ModelId", + column: x => x.ModelId, + principalTable: "Models", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "MotherboardModels", + columns: table => new + { + Id = table.Column(nullable: false), + ModelId = table.Column(nullable: true), + Version = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_MotherboardModels", x => x.Id); + table.ForeignKey( + name: "FK_MotherboardModels_Models_ModelId", + column: x => x.ModelId, + principalTable: "Models", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "RAMModels", + columns: table => new + { + Id = table.Column(nullable: false), + ModelId = table.Column(nullable: true), + Size = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_RAMModels", x => x.Id); + table.ForeignKey( + name: "FK_RAMModels_Models_ModelId", + column: x => x.ModelId, + principalTable: "Models", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "StorageModels", + columns: table => new + { + Id = table.Column(nullable: false), + ModelId = table.Column(nullable: true), + Size = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_StorageModels", x => x.Id); + table.ForeignKey( + name: "FK_StorageModels_Models_ModelId", + column: x => x.ModelId, + principalTable: "Models", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "Systems", + columns: table => new + { + Id = table.Column(nullable: false), + HostName = table.Column(nullable: true), + ModelId = table.Column(nullable: true), + Secret = table.Column(nullable: true), + LastUpdated = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Systems", x => x.Id); + table.ForeignKey( + name: "FK_Systems_Models_ModelId", + column: x => x.ModelId, + principalTable: "Models", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "ApplicationUserSystems", + columns: table => new + { + UserId = table.Column(nullable: false), + SystemId = table.Column(nullable: false), + CategoryId = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_ApplicationUserSystems", x => new { x.UserId, x.SystemId }); + table.ForeignKey( + name: "FK_ApplicationUserSystems_ApplicationUserSystemCategory_CategoryId", + column: x => x.CategoryId, + principalTable: "ApplicationUserSystemCategory", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_ApplicationUserSystems_Systems_SystemId", + column: x => x.SystemId, + principalTable: "Systems", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_ApplicationUserSystems_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "SystemBIOSs", + columns: table => new + { + SystemId = table.Column(nullable: false), + BIOSModelId = table.Column(nullable: false), + Caption = table.Column(nullable: true), + Version = table.Column(nullable: true), + Date = table.Column(nullable: true), + LastUpdated = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SystemBIOSs", x => x.SystemId); + table.ForeignKey( + name: "FK_SystemBIOSs_BIOSModels_BIOSModelId", + column: x => x.BIOSModelId, + principalTable: "BIOSModels", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_SystemBIOSs_Systems_SystemId", + column: x => x.SystemId, + principalTable: "Systems", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "SystemCommands", + columns: table => new + { + SystemId = table.Column(nullable: false), + Action = table.Column(nullable: true), + Properties = table.Column(nullable: true), + QueuedTime = table.Column(nullable: false), + ExecutedTime = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_SystemCommands", x => new { x.SystemId, x.QueuedTime }); + table.ForeignKey( + name: "FK_SystemCommands_Systems_SystemId", + column: x => x.SystemId, + principalTable: "Systems", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "SystemCPUs", + columns: table => new + { + SystemId = table.Column(nullable: false), + CPUModelID = table.Column(nullable: false), + Slot = table.Column(nullable: false), + ClockSpeed = table.Column(nullable: false), + CoreCount = table.Column(nullable: false), + ThreadCount = table.Column(nullable: false), + LastUpdated = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SystemCPUs", x => new { x.SystemId, x.CPUModelID, x.Slot }); + table.ForeignKey( + name: "FK_SystemCPUs_CPUModels_CPUModelID", + column: x => x.CPUModelID, + principalTable: "CPUModels", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_SystemCPUs_Systems_SystemId", + column: x => x.SystemId, + principalTable: "Systems", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "SystemCPUsData", + columns: table => new + { + SystemId = table.Column(nullable: false), + Load = table.Column(nullable: false), + Processes = table.Column(nullable: false), + CollectionDateTime = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SystemCPUsData", x => new { x.SystemId, x.CollectionDateTime }); + table.ForeignKey( + name: "FK_SystemCPUsData_Systems_SystemId", + column: x => x.SystemId, + principalTable: "Systems", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "SystemGPUs", + columns: table => new + { + SystemId = table.Column(nullable: false), + GPUModelId = table.Column(nullable: false), + Slot = table.Column(nullable: false), + LastUpdated = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SystemGPUs", x => new { x.SystemId, x.GPUModelId, x.Slot }); + table.ForeignKey( + name: "FK_SystemGPUs_GPUModels_GPUModelId", + column: x => x.GPUModelId, + principalTable: "GPUModels", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_SystemGPUs_Systems_SystemId", + column: x => x.SystemId, + principalTable: "Systems", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "SystemMotherboards", + columns: table => new + { + SystemId = table.Column(nullable: false), + MotherboardModelId = table.Column(nullable: false), + LastUpdated = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SystemMotherboards", x => x.SystemId); + table.ForeignKey( + name: "FK_SystemMotherboards_MotherboardModels_MotherboardModelId", + column: x => x.MotherboardModelId, + principalTable: "MotherboardModels", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_SystemMotherboards_Systems_SystemId", + column: x => x.SystemId, + principalTable: "Systems", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "SystemOSs", + columns: table => new + { + SystemId = table.Column(nullable: false), + OperatingSystemId = table.Column(nullable: false), + ArchitectureId = table.Column(nullable: true), + Version = table.Column(nullable: true), + LastUpdated = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SystemOSs", x => new { x.SystemId, x.OperatingSystemId }); + table.ForeignKey( + name: "FK_SystemOSs_Architectures_ArchitectureId", + column: x => x.ArchitectureId, + principalTable: "Architectures", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_SystemOSs_OperatingSystemModels_OperatingSystemId", + column: x => x.OperatingSystemId, + principalTable: "OperatingSystemModels", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_SystemOSs_Systems_SystemId", + column: x => x.SystemId, + principalTable: "Systems", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "SystemPingData", + columns: table => new + { + SystemId = table.Column(nullable: false), + SendPingTime = table.Column(nullable: false), + CollectionDateTime = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SystemPingData", x => new { x.SystemId, x.SendPingTime }); + table.ForeignKey( + name: "FK_SystemPingData_Systems_SystemId", + column: x => x.SystemId, + principalTable: "Systems", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "SystemRAMData", + columns: table => new + { + SystemId = table.Column(nullable: false), + Free = table.Column(nullable: false), + CollectionDateTime = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SystemRAMData", x => new { x.SystemId, x.CollectionDateTime }); + table.ForeignKey( + name: "FK_SystemRAMData_Systems_SystemId", + column: x => x.SystemId, + principalTable: "Systems", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "SystemRAMs", + columns: table => new + { + SystemId = table.Column(nullable: false), + RAMModelId = table.Column(nullable: false), + Slot = table.Column(nullable: false), + Speed = table.Column(nullable: false), + LastUpdated = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SystemRAMs", x => new { x.SystemId, x.RAMModelId, x.Slot }); + table.ForeignKey( + name: "FK_SystemRAMs_RAMModels_RAMModelId", + column: x => x.RAMModelId, + principalTable: "RAMModels", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_SystemRAMs_Systems_SystemId", + column: x => x.SystemId, + principalTable: "Systems", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "SystemRunningProcesses", + columns: table => new + { + SystemId = table.Column(nullable: false), + Id = table.Column(nullable: false), + Name = table.Column(nullable: true), + MemSize = table.Column(nullable: false), + KernelTime = table.Column(nullable: false), + Path = table.Column(nullable: true), + Threads = table.Column(nullable: false), + UpTime = table.Column(nullable: false), + ParentId = table.Column(nullable: false), + CollectionDateTime = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SystemRunningProcesses", x => new { x.SystemId, x.Id, x.CollectionDateTime }); + table.ForeignKey( + name: "FK_SystemRunningProcesses_Systems_SystemId", + column: x => x.SystemId, + principalTable: "Systems", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "SystemStorageData", + columns: table => new + { + SystemId = table.Column(nullable: false), + Time = table.Column(nullable: false), + Transfers = table.Column(nullable: false), + Reads = table.Column(nullable: false), + Writes = table.Column(nullable: false), + ByteReads = table.Column(nullable: false), + ByteWrites = table.Column(nullable: false), + CollectionDateTime = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SystemStorageData", x => new { x.SystemId, x.CollectionDateTime }); + table.ForeignKey( + name: "FK_SystemStorageData_Systems_SystemId", + column: x => x.SystemId, + principalTable: "Systems", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "SystemStorages", + columns: table => new + { + SystemId = table.Column(nullable: false), + StorageModelId = table.Column(nullable: false), + Slot = table.Column(nullable: false), + StorageInterfaceId = table.Column(nullable: true), + LastUpdated = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SystemStorages", x => new { x.SystemId, x.StorageModelId, x.Slot }); + table.ForeignKey( + name: "FK_SystemStorages_StorageInterfaceTypes_StorageInterfaceId", + column: x => x.StorageInterfaceId, + principalTable: "StorageInterfaceTypes", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_SystemStorages_StorageModels_StorageModelId", + column: x => x.StorageModelId, + principalTable: "StorageModels", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_SystemStorages_Systems_SystemId", + column: x => x.SystemId, + principalTable: "Systems", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "SystemTypes", + columns: table => new + { + SystemId = table.Column(nullable: false), + TypeId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SystemTypes", x => new { x.SystemId, x.TypeId }); + table.ForeignKey( + name: "FK_SystemTypes_Systems_SystemId", + column: x => x.SystemId, + principalTable: "Systems", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_SystemTypes_SystemTypeNames_TypeId", + column: x => x.TypeId, + principalTable: "SystemTypeNames", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_ApplicationUserSystems_CategoryId", + table: "ApplicationUserSystems", + column: "CategoryId"); + + migrationBuilder.CreateIndex( + name: "IX_ApplicationUserSystems_SystemId", + table: "ApplicationUserSystems", + column: "SystemId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetRoleClaims_RoleId", + table: "AspNetRoleClaims", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "RoleNameIndex", + table: "AspNetRoles", + column: "NormalizedName", + unique: true, + filter: "[NormalizedName] IS NOT NULL"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserClaims_UserId", + table: "AspNetUserClaims", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserLogins_UserId", + table: "AspNetUserLogins", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserRoles_RoleId", + table: "AspNetUserRoles", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "EmailIndex", + table: "AspNetUsers", + column: "NormalizedEmail"); + + migrationBuilder.CreateIndex( + name: "UserNameIndex", + table: "AspNetUsers", + column: "NormalizedUserName", + unique: true, + filter: "[NormalizedUserName] IS NOT NULL"); + + migrationBuilder.CreateIndex( + name: "IX_AuthenticationTokens_UserId", + table: "AuthenticationTokens", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_BIOSModels_ManufacturerId", + table: "BIOSModels", + column: "ManufacturerId"); + + migrationBuilder.CreateIndex( + name: "IX_CPUModels_ArchitectureId", + table: "CPUModels", + column: "ArchitectureId"); + + migrationBuilder.CreateIndex( + name: "IX_CPUModels_ModelId", + table: "CPUModels", + column: "ModelId", + unique: true, + filter: "[ModelId] IS NOT NULL"); + + migrationBuilder.CreateIndex( + name: "IX_GPUModels_ModelId", + table: "GPUModels", + column: "ModelId", + unique: true, + filter: "[ModelId] IS NOT NULL"); + + migrationBuilder.CreateIndex( + name: "IX_Models_ManufacturerId", + table: "Models", + column: "ManufacturerId"); + + migrationBuilder.CreateIndex( + name: "IX_MotherboardModels_ModelId", + table: "MotherboardModels", + column: "ModelId", + unique: true, + filter: "[ModelId] IS NOT NULL"); + + migrationBuilder.CreateIndex( + name: "IX_RAMModels_ModelId", + table: "RAMModels", + column: "ModelId", + unique: true, + filter: "[ModelId] IS NOT NULL"); + + migrationBuilder.CreateIndex( + name: "IX_StorageModels_ModelId", + table: "StorageModels", + column: "ModelId", + unique: true, + filter: "[ModelId] IS NOT NULL"); + + migrationBuilder.CreateIndex( + name: "IX_SystemBIOSs_BIOSModelId", + table: "SystemBIOSs", + column: "BIOSModelId"); + + migrationBuilder.CreateIndex( + name: "IX_SystemCPUs_CPUModelID", + table: "SystemCPUs", + column: "CPUModelID"); + + migrationBuilder.CreateIndex( + name: "IX_SystemGPUs_GPUModelId", + table: "SystemGPUs", + column: "GPUModelId"); + + migrationBuilder.CreateIndex( + name: "IX_SystemMotherboards_MotherboardModelId", + table: "SystemMotherboards", + column: "MotherboardModelId"); + + migrationBuilder.CreateIndex( + name: "IX_SystemOSs_ArchitectureId", + table: "SystemOSs", + column: "ArchitectureId"); + + migrationBuilder.CreateIndex( + name: "IX_SystemOSs_OperatingSystemId", + table: "SystemOSs", + column: "OperatingSystemId"); + + migrationBuilder.CreateIndex( + name: "IX_SystemRAMs_RAMModelId", + table: "SystemRAMs", + column: "RAMModelId"); + + migrationBuilder.CreateIndex( + name: "IX_Systems_ModelId", + table: "Systems", + column: "ModelId"); + + migrationBuilder.CreateIndex( + name: "IX_SystemStorages_StorageInterfaceId", + table: "SystemStorages", + column: "StorageInterfaceId"); + + migrationBuilder.CreateIndex( + name: "IX_SystemStorages_StorageModelId", + table: "SystemStorages", + column: "StorageModelId"); + + migrationBuilder.CreateIndex( + name: "IX_SystemTypes_TypeId", + table: "SystemTypes", + column: "TypeId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "ApplicationUserSystems"); + + migrationBuilder.DropTable( + name: "AspNetRoleClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserLogins"); + + migrationBuilder.DropTable( + name: "AspNetUserRoles"); + + migrationBuilder.DropTable( + name: "AspNetUserTokens"); + + migrationBuilder.DropTable( + name: "AuthenticationTokens"); + + migrationBuilder.DropTable( + name: "SystemBIOSs"); + + migrationBuilder.DropTable( + name: "SystemCommands"); + + migrationBuilder.DropTable( + name: "SystemCPUs"); + + migrationBuilder.DropTable( + name: "SystemCPUsData"); + + migrationBuilder.DropTable( + name: "SystemGPUs"); + + migrationBuilder.DropTable( + name: "SystemMotherboards"); + + migrationBuilder.DropTable( + name: "SystemOSs"); + + migrationBuilder.DropTable( + name: "SystemPingData"); + + migrationBuilder.DropTable( + name: "SystemRAMData"); + + migrationBuilder.DropTable( + name: "SystemRAMs"); + + migrationBuilder.DropTable( + name: "SystemRunningProcesses"); + + migrationBuilder.DropTable( + name: "SystemStorageData"); + + migrationBuilder.DropTable( + name: "SystemStorages"); + + migrationBuilder.DropTable( + name: "SystemTypes"); + + migrationBuilder.DropTable( + name: "ApplicationUserSystemCategory"); + + migrationBuilder.DropTable( + name: "AspNetRoles"); + + migrationBuilder.DropTable( + name: "AspNetUsers"); + + migrationBuilder.DropTable( + name: "BIOSModels"); + + migrationBuilder.DropTable( + name: "CPUModels"); + + migrationBuilder.DropTable( + name: "GPUModels"); + + migrationBuilder.DropTable( + name: "MotherboardModels"); + + migrationBuilder.DropTable( + name: "OperatingSystemModels"); + + migrationBuilder.DropTable( + name: "RAMModels"); + + migrationBuilder.DropTable( + name: "StorageInterfaceTypes"); + + migrationBuilder.DropTable( + name: "StorageModels"); + + migrationBuilder.DropTable( + name: "Systems"); + + migrationBuilder.DropTable( + name: "SystemTypeNames"); + + migrationBuilder.DropTable( + name: "Architectures"); + + migrationBuilder.DropTable( + name: "Models"); + + migrationBuilder.DropTable( + name: "Manufacturers"); + } + } +} diff --git a/syski_api/uk.co.syski.data/Migrations/SyskiDBContextModelSnapshot.cs b/syski_api/uk.co.syski.data/Migrations/SyskiDBContextModelSnapshot.cs new file mode 100644 index 0000000..5a3c36a --- /dev/null +++ b/syski_api/uk.co.syski.data/Migrations/SyskiDBContextModelSnapshot.cs @@ -0,0 +1,1013 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Syski.Data; + +namespace Syski.Data.Migrations +{ + [DbContext(typeof(SyskiDBContext))] + partial class SyskiDBContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.1.8-servicing-32085") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Name") + .HasMaxLength(256); + + b.Property("NormalizedName") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("RoleId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasMaxLength(128); + + b.Property("ProviderKey") + .HasMaxLength(128); + + b.Property("ProviderDisplayName"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId"); + + b.Property("RoleId"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId"); + + b.Property("LoginProvider") + .HasMaxLength(128); + + b.Property("Name") + .HasMaxLength(128); + + b.Property("Value"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Syski.Data.ApplicationUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AccessFailedCount"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Email") + .HasMaxLength(256); + + b.Property("EmailConfirmed"); + + b.Property("LockoutEnabled"); + + b.Property("LockoutEnd"); + + b.Property("NormalizedEmail") + .HasMaxLength(256); + + b.Property("NormalizedUserName") + .HasMaxLength(256); + + b.Property("PasswordHash"); + + b.Property("PhoneNumber"); + + b.Property("PhoneNumberConfirmed"); + + b.Property("SecurityStamp"); + + b.Property("TwoFactorEnabled"); + + b.Property("UserName") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Syski.Data.ApplicationUserSystemCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Name"); + + b.HasKey("Id"); + + b.ToTable("ApplicationUserSystemCategory"); + }); + + modelBuilder.Entity("Syski.Data.ApplicationUserSystems", b => + { + b.Property("UserId"); + + b.Property("SystemId"); + + b.Property("CategoryId"); + + b.HasKey("UserId", "SystemId"); + + b.HasIndex("CategoryId"); + + b.HasIndex("SystemId"); + + b.ToTable("ApplicationUserSystems"); + }); + + modelBuilder.Entity("Syski.Data.Architecture", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Name"); + + b.HasKey("Id"); + + b.ToTable("Architectures"); + }); + + modelBuilder.Entity("Syski.Data.AuthenticationToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("Audience"); + + b.Property("Expires"); + + b.Property("Issuer"); + + b.Property("NextTokenId"); + + b.Property("NotBefore"); + + b.Property("PreviousTokenId"); + + b.Property("RefreshToken"); + + b.Property("Subject"); + + b.Property("TokenType"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AuthenticationTokens"); + }); + + modelBuilder.Entity("Syski.Data.BIOSModel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ManufacturerId"); + + b.HasKey("Id"); + + b.HasIndex("ManufacturerId"); + + b.ToTable("BIOSModels"); + }); + + modelBuilder.Entity("Syski.Data.CPUModel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ArchitectureId"); + + b.Property("ModelId"); + + b.HasKey("Id"); + + b.HasIndex("ArchitectureId"); + + b.HasIndex("ModelId") + .IsUnique() + .HasFilter("[ModelId] IS NOT NULL"); + + b.ToTable("CPUModels"); + }); + + modelBuilder.Entity("Syski.Data.GPUModel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ModelId"); + + b.HasKey("Id"); + + b.HasIndex("ModelId") + .IsUnique() + .HasFilter("[ModelId] IS NOT NULL"); + + b.ToTable("GPUModels"); + }); + + modelBuilder.Entity("Syski.Data.Manufacturer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Name"); + + b.HasKey("Id"); + + b.ToTable("Manufacturers"); + }); + + modelBuilder.Entity("Syski.Data.Model", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ManufacturerId"); + + b.Property("Name"); + + b.HasKey("Id"); + + b.HasIndex("ManufacturerId"); + + b.ToTable("Models"); + }); + + modelBuilder.Entity("Syski.Data.MotherboardModel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ModelId"); + + b.Property("Version"); + + b.HasKey("Id"); + + b.HasIndex("ModelId") + .IsUnique() + .HasFilter("[ModelId] IS NOT NULL"); + + b.ToTable("MotherboardModels"); + }); + + modelBuilder.Entity("Syski.Data.OperatingSystemModel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Name"); + + b.HasKey("Id"); + + b.ToTable("OperatingSystemModels"); + }); + + modelBuilder.Entity("Syski.Data.RAMModel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ModelId"); + + b.Property("Size"); + + b.HasKey("Id"); + + b.HasIndex("ModelId") + .IsUnique() + .HasFilter("[ModelId] IS NOT NULL"); + + b.ToTable("RAMModels"); + }); + + modelBuilder.Entity("Syski.Data.StorageInterfaceType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Name"); + + b.HasKey("Id"); + + b.ToTable("StorageInterfaceTypes"); + }); + + modelBuilder.Entity("Syski.Data.StorageModel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ModelId"); + + b.Property("Size"); + + b.HasKey("Id"); + + b.HasIndex("ModelId") + .IsUnique() + .HasFilter("[ModelId] IS NOT NULL"); + + b.ToTable("StorageModels"); + }); + + modelBuilder.Entity("Syski.Data.System", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("HostName"); + + b.Property("LastUpdated"); + + b.Property("ModelId"); + + b.Property("Secret"); + + b.HasKey("Id"); + + b.HasIndex("ModelId"); + + b.ToTable("Systems"); + }); + + modelBuilder.Entity("Syski.Data.SystemBIOS", b => + { + b.Property("SystemId"); + + b.Property("BIOSModelId"); + + b.Property("Caption"); + + b.Property("Date"); + + b.Property("LastUpdated"); + + b.Property("Version"); + + b.HasKey("SystemId"); + + b.HasIndex("BIOSModelId"); + + b.ToTable("SystemBIOSs"); + }); + + modelBuilder.Entity("Syski.Data.SystemCommand", b => + { + b.Property("SystemId"); + + b.Property("QueuedTime"); + + b.Property("Action"); + + b.Property("ExecutedTime"); + + b.Property("Properties"); + + b.HasKey("SystemId", "QueuedTime"); + + b.ToTable("SystemCommands"); + }); + + modelBuilder.Entity("Syski.Data.SystemCPU", b => + { + b.Property("SystemId"); + + b.Property("CPUModelID"); + + b.Property("Slot"); + + b.Property("ClockSpeed"); + + b.Property("CoreCount"); + + b.Property("LastUpdated"); + + b.Property("ThreadCount"); + + b.HasKey("SystemId", "CPUModelID", "Slot"); + + b.HasIndex("CPUModelID"); + + b.ToTable("SystemCPUs"); + }); + + modelBuilder.Entity("Syski.Data.SystemCPUData", b => + { + b.Property("SystemId"); + + b.Property("CollectionDateTime"); + + b.Property("Load"); + + b.Property("Processes"); + + b.HasKey("SystemId", "CollectionDateTime"); + + b.ToTable("SystemCPUsData"); + }); + + modelBuilder.Entity("Syski.Data.SystemGPU", b => + { + b.Property("SystemId"); + + b.Property("GPUModelId"); + + b.Property("Slot"); + + b.Property("LastUpdated"); + + b.HasKey("SystemId", "GPUModelId", "Slot"); + + b.HasIndex("GPUModelId"); + + b.ToTable("SystemGPUs"); + }); + + modelBuilder.Entity("Syski.Data.SystemMotherboard", b => + { + b.Property("SystemId"); + + b.Property("LastUpdated"); + + b.Property("MotherboardModelId"); + + b.HasKey("SystemId"); + + b.HasIndex("MotherboardModelId"); + + b.ToTable("SystemMotherboards"); + }); + + modelBuilder.Entity("Syski.Data.SystemOS", b => + { + b.Property("SystemId"); + + b.Property("OperatingSystemId"); + + b.Property("ArchitectureId"); + + b.Property("LastUpdated"); + + b.Property("Version"); + + b.HasKey("SystemId", "OperatingSystemId"); + + b.HasIndex("ArchitectureId"); + + b.HasIndex("OperatingSystemId"); + + b.ToTable("SystemOSs"); + }); + + modelBuilder.Entity("Syski.Data.SystemPingData", b => + { + b.Property("SystemId"); + + b.Property("SendPingTime"); + + b.Property("CollectionDateTime"); + + b.HasKey("SystemId", "SendPingTime"); + + b.ToTable("SystemPingData"); + }); + + modelBuilder.Entity("Syski.Data.SystemRAM", b => + { + b.Property("SystemId"); + + b.Property("RAMModelId"); + + b.Property("Slot"); + + b.Property("LastUpdated"); + + b.Property("Speed"); + + b.HasKey("SystemId", "RAMModelId", "Slot"); + + b.HasIndex("RAMModelId"); + + b.ToTable("SystemRAMs"); + }); + + modelBuilder.Entity("Syski.Data.SystemRAMData", b => + { + b.Property("SystemId"); + + b.Property("CollectionDateTime"); + + b.Property("Free"); + + b.HasKey("SystemId", "CollectionDateTime"); + + b.ToTable("SystemRAMData"); + }); + + modelBuilder.Entity("Syski.Data.SystemRunningProcesses", b => + { + b.Property("SystemId"); + + b.Property("Id"); + + b.Property("CollectionDateTime"); + + b.Property("KernelTime"); + + b.Property("MemSize"); + + b.Property("Name"); + + b.Property("ParentId"); + + b.Property("Path"); + + b.Property("Threads"); + + b.Property("UpTime"); + + b.HasKey("SystemId", "Id", "CollectionDateTime"); + + b.ToTable("SystemRunningProcesses"); + }); + + modelBuilder.Entity("Syski.Data.SystemStorage", b => + { + b.Property("SystemId"); + + b.Property("StorageModelId"); + + b.Property("Slot"); + + b.Property("LastUpdated"); + + b.Property("StorageInterfaceId"); + + b.HasKey("SystemId", "StorageModelId", "Slot"); + + b.HasIndex("StorageInterfaceId"); + + b.HasIndex("StorageModelId"); + + b.ToTable("SystemStorages"); + }); + + modelBuilder.Entity("Syski.Data.SystemStorageData", b => + { + b.Property("SystemId"); + + b.Property("CollectionDateTime"); + + b.Property("ByteReads"); + + b.Property("ByteWrites"); + + b.Property("Reads"); + + b.Property("Time"); + + b.Property("Transfers"); + + b.Property("Writes"); + + b.HasKey("SystemId", "CollectionDateTime"); + + b.ToTable("SystemStorageData"); + }); + + modelBuilder.Entity("Syski.Data.SystemType", b => + { + b.Property("SystemId"); + + b.Property("TypeId"); + + b.HasKey("SystemId", "TypeId"); + + b.HasIndex("TypeId"); + + b.ToTable("SystemTypes"); + }); + + modelBuilder.Entity("Syski.Data.SystemTypeName", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Name"); + + b.HasKey("Id"); + + b.ToTable("SystemTypeNames"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Syski.Data.ApplicationUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Syski.Data.ApplicationUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Syski.Data.ApplicationUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Syski.Data.ApplicationUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Syski.Data.ApplicationUserSystems", b => + { + b.HasOne("Syski.Data.ApplicationUserSystemCategory", "Category") + .WithMany("Systems") + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Syski.Data.System", "System") + .WithMany("Users") + .HasForeignKey("SystemId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Syski.Data.ApplicationUser", "User") + .WithMany("Systems") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict); + }); + + modelBuilder.Entity("Syski.Data.AuthenticationToken", b => + { + b.HasOne("Syski.Data.ApplicationUser", "User") + .WithMany("AuthenticationTokens") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict); + }); + + modelBuilder.Entity("Syski.Data.BIOSModel", b => + { + b.HasOne("Syski.Data.Manufacturer", "Manufacturer") + .WithMany("BIOSManufacturer") + .HasForeignKey("ManufacturerId") + .OnDelete(DeleteBehavior.Restrict); + }); + + modelBuilder.Entity("Syski.Data.CPUModel", b => + { + b.HasOne("Syski.Data.Architecture", "Architecture") + .WithMany("CPUModels") + .HasForeignKey("ArchitectureId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Syski.Data.Model", "Model") + .WithOne("CPUModel") + .HasForeignKey("Syski.Data.CPUModel", "ModelId") + .OnDelete(DeleteBehavior.Restrict); + }); + + modelBuilder.Entity("Syski.Data.GPUModel", b => + { + b.HasOne("Syski.Data.Model", "Model") + .WithOne("GPUModel") + .HasForeignKey("Syski.Data.GPUModel", "ModelId") + .OnDelete(DeleteBehavior.Restrict); + }); + + modelBuilder.Entity("Syski.Data.Model", b => + { + b.HasOne("Syski.Data.Manufacturer", "Manufacturer") + .WithMany("Models") + .HasForeignKey("ManufacturerId") + .OnDelete(DeleteBehavior.Restrict); + }); + + modelBuilder.Entity("Syski.Data.MotherboardModel", b => + { + b.HasOne("Syski.Data.Model", "Model") + .WithOne("MotherboardModel") + .HasForeignKey("Syski.Data.MotherboardModel", "ModelId") + .OnDelete(DeleteBehavior.Restrict); + }); + + modelBuilder.Entity("Syski.Data.RAMModel", b => + { + b.HasOne("Syski.Data.Model", "Model") + .WithOne("RAMModel") + .HasForeignKey("Syski.Data.RAMModel", "ModelId") + .OnDelete(DeleteBehavior.Restrict); + }); + + modelBuilder.Entity("Syski.Data.StorageModel", b => + { + b.HasOne("Syski.Data.Model", "Model") + .WithOne("StorageModel") + .HasForeignKey("Syski.Data.StorageModel", "ModelId") + .OnDelete(DeleteBehavior.Restrict); + }); + + modelBuilder.Entity("Syski.Data.System", b => + { + b.HasOne("Syski.Data.Model", "Model") + .WithMany("Systems") + .HasForeignKey("ModelId") + .OnDelete(DeleteBehavior.Restrict); + }); + + modelBuilder.Entity("Syski.Data.SystemBIOS", b => + { + b.HasOne("Syski.Data.BIOSModel", "BIOSModel") + .WithMany("SystemBIOSs") + .HasForeignKey("BIOSModelId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Syski.Data.System", "System") + .WithOne("SystemBIOS") + .HasForeignKey("Syski.Data.SystemBIOS", "SystemId") + .OnDelete(DeleteBehavior.Restrict); + }); + + modelBuilder.Entity("Syski.Data.SystemCommand", b => + { + b.HasOne("Syski.Data.System", "System") + .WithMany("SystemCommands") + .HasForeignKey("SystemId") + .OnDelete(DeleteBehavior.Restrict); + }); + + modelBuilder.Entity("Syski.Data.SystemCPU", b => + { + b.HasOne("Syski.Data.CPUModel", "CPUModel") + .WithMany("SystemCPUs") + .HasForeignKey("CPUModelID") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Syski.Data.System", "System") + .WithMany("SystemCPUs") + .HasForeignKey("SystemId") + .OnDelete(DeleteBehavior.Restrict); + }); + + modelBuilder.Entity("Syski.Data.SystemCPUData", b => + { + b.HasOne("Syski.Data.System", "System") + .WithMany("SystemCPUData") + .HasForeignKey("SystemId") + .OnDelete(DeleteBehavior.Restrict); + }); + + modelBuilder.Entity("Syski.Data.SystemGPU", b => + { + b.HasOne("Syski.Data.GPUModel", "GPUModel") + .WithMany("SystemGPUs") + .HasForeignKey("GPUModelId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Syski.Data.System", "System") + .WithMany("SystemGPUs") + .HasForeignKey("SystemId") + .OnDelete(DeleteBehavior.Restrict); + }); + + modelBuilder.Entity("Syski.Data.SystemMotherboard", b => + { + b.HasOne("Syski.Data.MotherboardModel", "MotherboardModel") + .WithMany("SystemMotherboards") + .HasForeignKey("MotherboardModelId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Syski.Data.System", "System") + .WithOne("SystemMotherboard") + .HasForeignKey("Syski.Data.SystemMotherboard", "SystemId") + .OnDelete(DeleteBehavior.Restrict); + }); + + modelBuilder.Entity("Syski.Data.SystemOS", b => + { + b.HasOne("Syski.Data.Architecture", "Architecture") + .WithMany("SystemOSs") + .HasForeignKey("ArchitectureId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Syski.Data.OperatingSystemModel", "OperatingSystem") + .WithMany("SystemOSs") + .HasForeignKey("OperatingSystemId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Syski.Data.System", "System") + .WithMany("SystemOSs") + .HasForeignKey("SystemId") + .OnDelete(DeleteBehavior.Restrict); + }); + + modelBuilder.Entity("Syski.Data.SystemPingData", b => + { + b.HasOne("Syski.Data.System", "System") + .WithMany("SystemPingData") + .HasForeignKey("SystemId") + .OnDelete(DeleteBehavior.Restrict); + }); + + modelBuilder.Entity("Syski.Data.SystemRAM", b => + { + b.HasOne("Syski.Data.RAMModel", "RAMModel") + .WithMany("SystemRAMs") + .HasForeignKey("RAMModelId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Syski.Data.System", "System") + .WithMany("SystemRAMs") + .HasForeignKey("SystemId") + .OnDelete(DeleteBehavior.Restrict); + }); + + modelBuilder.Entity("Syski.Data.SystemRAMData", b => + { + b.HasOne("Syski.Data.System", "System") + .WithMany("SystemRAMData") + .HasForeignKey("SystemId") + .OnDelete(DeleteBehavior.Restrict); + }); + + modelBuilder.Entity("Syski.Data.SystemRunningProcesses", b => + { + b.HasOne("Syski.Data.System", "System") + .WithMany("SystemRunningProcesses") + .HasForeignKey("SystemId") + .OnDelete(DeleteBehavior.Restrict); + }); + + modelBuilder.Entity("Syski.Data.SystemStorage", b => + { + b.HasOne("Syski.Data.StorageInterfaceType", "StorageInterface") + .WithMany("SystemStorages") + .HasForeignKey("StorageInterfaceId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Syski.Data.StorageModel", "StorageModel") + .WithMany("SystemStorages") + .HasForeignKey("StorageModelId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Syski.Data.System", "System") + .WithMany("SystemStorages") + .HasForeignKey("SystemId") + .OnDelete(DeleteBehavior.Restrict); + }); + + modelBuilder.Entity("Syski.Data.SystemStorageData", b => + { + b.HasOne("Syski.Data.System", "System") + .WithMany("SystemStorageData") + .HasForeignKey("SystemId") + .OnDelete(DeleteBehavior.Restrict); + }); + + modelBuilder.Entity("Syski.Data.SystemType", b => + { + b.HasOne("Syski.Data.System", "System") + .WithMany("SystemTypes") + .HasForeignKey("SystemId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Syski.Data.SystemTypeName", "Type") + .WithMany("SystemTypes") + .HasForeignKey("TypeId") + .OnDelete(DeleteBehavior.Restrict); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/syski_api/uk.co.syski.data/Model.cs b/syski_api/uk.co.syski.data/Model.cs new file mode 100644 index 0000000..d59ca48 --- /dev/null +++ b/syski_api/uk.co.syski.data/Model.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Syski.Data +{ + public class Model + { + + public Guid Id { get; set; } + + public string Name { get; set; } + + public Guid? ManufacturerId { get; set; } + + public Manufacturer Manufacturer { get; set; } + + public IEnumerable Systems { get; set; } + + public CPUModel CPUModel { get; set; } + + public RAMModel RAMModel { get; set; } + + public GPUModel GPUModel { get; set; } + + public StorageModel StorageModel { get; set; } + + public MotherboardModel MotherboardModel { get; set; } + + } +} diff --git a/syski_api/uk.co.syski.data/MotherboardModel.cs b/syski_api/uk.co.syski.data/MotherboardModel.cs new file mode 100644 index 0000000..9101a08 --- /dev/null +++ b/syski_api/uk.co.syski.data/MotherboardModel.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Syski.Data +{ + public class MotherboardModel + { + + public Guid Id { get; set; } + + public Guid? ModelId { get; set; } + + public Model Model { get; set; } + + public string Version { get; set; } + + public IEnumerable SystemMotherboards { get; set; } + + } +} diff --git a/syski_api/uk.co.syski.data/OperatingSystemModel.cs b/syski_api/uk.co.syski.data/OperatingSystemModel.cs new file mode 100644 index 0000000..b10c2d6 --- /dev/null +++ b/syski_api/uk.co.syski.data/OperatingSystemModel.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Syski.Data +{ + public class OperatingSystemModel + { + + public Guid Id { get; set; } + + public string Name { get; set; } + + public List SystemOSs { get; set; } + + } +} diff --git a/syski_api/uk.co.syski.data/RAMModel.cs b/syski_api/uk.co.syski.data/RAMModel.cs new file mode 100644 index 0000000..9b050b1 --- /dev/null +++ b/syski_api/uk.co.syski.data/RAMModel.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Syski.Data +{ + public class RAMModel + { + + public Guid Id { get; set; } + + public Guid? ModelId { get; set; } + + public Model Model { get; set; } + + public long Size { get; set; } + + public IEnumerable SystemRAMs { get; set; } + + } +} diff --git a/syski_api/uk.co.syski.data/StorageInterfaceType.cs b/syski_api/uk.co.syski.data/StorageInterfaceType.cs new file mode 100644 index 0000000..2ddf895 --- /dev/null +++ b/syski_api/uk.co.syski.data/StorageInterfaceType.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Syski.Data +{ + public class StorageInterfaceType + { + + public Guid Id { get; set; } + + public string Name { get; set; } + + public IEnumerable SystemStorages { get; set; } + + } +} diff --git a/syski_api/uk.co.syski.data/StorageModel.cs b/syski_api/uk.co.syski.data/StorageModel.cs new file mode 100644 index 0000000..e6da110 --- /dev/null +++ b/syski_api/uk.co.syski.data/StorageModel.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Syski.Data +{ + public class StorageModel + { + + public Guid Id { get; set; } + + public Guid? ModelId { get; set; } + + public Model Model { get; set; } + + public long Size { get; set; } + + public IEnumerable SystemStorages { get; set; } + + } +} diff --git a/syski_api/uk.co.syski.data/Syski.Data.csproj b/syski_api/uk.co.syski.data/Syski.Data.csproj new file mode 100644 index 0000000..3930282 --- /dev/null +++ b/syski_api/uk.co.syski.data/Syski.Data.csproj @@ -0,0 +1,37 @@ + + + + netcoreapp2.1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/syski_api/uk.co.syski.data/SyskiDBContext.cs b/syski_api/uk.co.syski.data/SyskiDBContext.cs new file mode 100644 index 0000000..200e43e --- /dev/null +++ b/syski_api/uk.co.syski.data/SyskiDBContext.cs @@ -0,0 +1,366 @@ +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; + +namespace Syski.Data +{ + public class SyskiDBContext : IdentityDbContext + { + + public DbSet AuthenticationTokens { get; set; } + public DbSet ApplicationUserSystems { get; set; } + + public DbSet Manufacturers { get; set; } + + public DbSet Models { get; set; } + + public DbSet Systems { get; set; } + + public DbSet SystemTypes { get; set; } + public DbSet SystemTypeNames { get; set; } + + public DbSet SystemCPUs { get; set; } + public DbSet CPUModels { get; set; } + + public DbSet SystemRAMs { get; set; } + public DbSet RAMModels { get; set; } + + public DbSet SystemGPUs { get; set; } + public DbSet GPUModels { get; set; } + + public DbSet SystemStorages { get; set; } + public DbSet StorageModels { get; set; } + public DbSet StorageInterfaceTypes { get; set; } + + public DbSet SystemMotherboards { get; set; } + public DbSet MotherboardModels { get; set; } + + public DbSet SystemBIOSs { get; set; } + public DbSet BIOSModels { get; set; } + + public DbSet SystemOSs { get; set; } + public DbSet OperatingSystemModels { get; set; } + + public DbSet Architectures { get; set; } + + public DbSet SystemPingData { get; set; } + + public DbSet SystemCPUsData { get; set; } + public DbSet SystemRAMData { get; set; } + public DbSet SystemStorageData { get; set; } + + public DbSet SystemRunningProcesses { get; set; } + + public DbSet SystemCommands { get; set; } + + public SyskiDBContext(DbContextOptions options) : base(options) + { + + } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + + var authenticationTokenEntity = builder.Entity(); + + authenticationTokenEntity.HasOne(t => t.User) + .WithMany(u => u.AuthenticationTokens) + .HasForeignKey(f => f.UserId) + .IsRequired() + .OnDelete(DeleteBehavior.Restrict); + + //authenticationTokenEntity.HasOne(t => t.NextToken) + // .WithOne(t => t.PreviousToken) + // .HasForeignKey(t => t.NextTokenId); + + //authenticationTokenEntity.HasOne(t => t.PreviousToken) + // .WithOne(t => t.NextToken) + // .HasForeignKey(t => t.PreviousTokenId); + + var applicationUserSystemsEntity = builder.Entity(); + + applicationUserSystemsEntity.HasOne(aus => aus.System) + .WithMany(s => s.Users) + .HasForeignKey(aus => aus.SystemId) + .IsRequired() + .OnDelete(DeleteBehavior.Restrict); + + applicationUserSystemsEntity.HasOne(aus => aus.User) + .WithMany(au => au.Systems) + .HasForeignKey(aus => aus.UserId) + .IsRequired() + .OnDelete(DeleteBehavior.Restrict); + + applicationUserSystemsEntity.HasOne(aus => aus.Category) + .WithMany(ausc => ausc.Systems) + .HasForeignKey(aus => aus.CategoryId) + .OnDelete(DeleteBehavior.Restrict); + + applicationUserSystemsEntity.HasKey(aus => new { aus.UserId, aus.SystemId }); + + var manufacturerEntity = builder.Entity(); + + manufacturerEntity.HasMany(m => m.Models) + .WithOne(m => m.Manufacturer) + .HasForeignKey(m => m.ManufacturerId) + .OnDelete(DeleteBehavior.Restrict); + + var systemEntity = builder.Entity(); + + systemEntity.HasOne(s => s.Model) + .WithMany(m => m.Systems) + .HasForeignKey(s => s.ModelId) + .OnDelete(DeleteBehavior.Restrict); + + var systemTypeEntity = builder.Entity(); + + systemTypeEntity.HasOne(st => st.System) + .WithMany(s => s.SystemTypes) + .HasForeignKey(st => st.SystemId) + .IsRequired() + .OnDelete(DeleteBehavior.Restrict); + + systemTypeEntity.HasOne(st => st.Type) + .WithMany(stn => stn.SystemTypes) + .HasForeignKey(st => st.TypeId) + .IsRequired() + .OnDelete(DeleteBehavior.Restrict); + + systemTypeEntity.HasKey(smt => new { smt.SystemId, smt.TypeId }); + + var systemCPUEntity = builder.Entity(); + + systemCPUEntity.HasOne(scpu => scpu.System) + .WithMany(s => s.SystemCPUs) + .HasForeignKey(scpu => scpu.SystemId) + .IsRequired() + .OnDelete(DeleteBehavior.Restrict); + + systemCPUEntity.HasOne(scpu => scpu.CPUModel) + .WithMany(cpum => cpum.SystemCPUs) + .HasForeignKey(scpu => scpu.CPUModelID) + .IsRequired() + .OnDelete(DeleteBehavior.Restrict); + + systemCPUEntity.HasKey(sc => new { sc.SystemId, sc.CPUModelID, sc.Slot }); + + var cpuModelEntity = builder.Entity(); + + cpuModelEntity.HasOne(cpum => cpum.Architecture) + .WithMany(a => a.CPUModels) + .HasForeignKey(cpum => cpum.ArchitectureId) + .OnDelete(DeleteBehavior.Restrict); + + cpuModelEntity.HasOne(cpum => cpum.Model) + .WithOne(m => m.CPUModel) + .HasForeignKey(cpum => cpum.ModelId) + .OnDelete(DeleteBehavior.Restrict); + + var systemRAMEntity = builder.Entity(); + + systemRAMEntity.HasOne(sram => sram.System) + .WithMany(s => s.SystemRAMs) + .HasForeignKey(sram => sram.SystemId) + .IsRequired() + .OnDelete(DeleteBehavior.Restrict); + + systemRAMEntity.HasOne(sram => sram.RAMModel) + .WithMany(ramm => ramm.SystemRAMs) + .HasForeignKey(sram => sram.RAMModelId) + .IsRequired() + .OnDelete(DeleteBehavior.Restrict); + + systemRAMEntity.HasKey(sr => new { sr.SystemId, sr.RAMModelId, sr.Slot }); + + var ramModelEntity = builder.Entity(); + + ramModelEntity.HasOne(ramm => ramm.Model) + .WithOne(m => m.RAMModel) + .HasForeignKey(ramm => ramm.ModelId) + .OnDelete(DeleteBehavior.Restrict); + + var systemGPUEntity = builder.Entity(); + + systemGPUEntity.HasOne(sgpu => sgpu.System) + .WithMany(s => s.SystemGPUs) + .HasForeignKey(sgpu => sgpu.SystemId) + .IsRequired() + .OnDelete(DeleteBehavior.Restrict); + + systemGPUEntity.HasOne(sgpu => sgpu.GPUModel) + .WithMany(gpum => gpum.SystemGPUs) + .HasForeignKey(sgpu => sgpu.GPUModelId) + .IsRequired() + .OnDelete(DeleteBehavior.Restrict); + + systemGPUEntity.HasKey(sg => new { sg.SystemId, sg.GPUModelId, sg.Slot }); + + var gpuModelEntity = builder.Entity(); + + gpuModelEntity.HasOne(gpum => gpum.Model) + .WithOne(m => m.GPUModel) + .HasForeignKey(gpum => gpum.ModelId) + .OnDelete(DeleteBehavior.Restrict); + + var systemStorage = builder.Entity(); + + systemStorage.HasOne(ss => ss.System) + .WithMany(s => s.SystemStorages) + .HasForeignKey(ss => ss.SystemId) + .IsRequired() + .OnDelete(DeleteBehavior.Restrict); + + systemStorage.HasOne(ss => ss.StorageModel) + .WithMany(sm => sm.SystemStorages) + .HasForeignKey(ss => ss.StorageModelId) + .IsRequired() + .OnDelete(DeleteBehavior.Restrict); + + systemStorage.HasOne(ss => ss.StorageInterface) + .WithMany(sit => sit.SystemStorages) + .HasForeignKey(ss => ss.StorageInterfaceId) + .OnDelete(DeleteBehavior.Restrict); + + systemStorage.HasKey(ss => new { ss.SystemId, ss.StorageModelId, ss.Slot }); + + var storageModelEntity = builder.Entity(); + + storageModelEntity.HasOne(sm => sm.Model) + .WithOne(m => m.StorageModel) + .HasForeignKey(sm => sm.ModelId) + .OnDelete(DeleteBehavior.Restrict); + + var systemMotherboard = builder.Entity(); + + systemMotherboard.HasOne(sm => sm.System) + .WithOne(sm => sm.SystemMotherboard) + .HasForeignKey(sm => sm.SystemId) + .IsRequired() + .OnDelete(DeleteBehavior.Restrict); + + systemMotherboard.HasOne(sm => sm.MotherboardModel) + .WithMany(sm => sm.SystemMotherboards) + .HasForeignKey(sm => sm.MotherboardModelId) + .IsRequired() + .OnDelete(DeleteBehavior.Restrict); + + systemMotherboard.HasKey(sc => new { sc.SystemId }); + + var motherboardModel = builder.Entity(); + + motherboardModel.HasOne(mm => mm.Model) + .WithOne(m => m.MotherboardModel) + .HasForeignKey(mm => mm.ModelId) + .OnDelete(DeleteBehavior.Restrict); + + var systemBIOS = builder.Entity(); + + systemBIOS.HasOne(sb => sb.System) + .WithOne(s => s.SystemBIOS) + .HasForeignKey(sb => sb.SystemId) + .IsRequired() + .OnDelete(DeleteBehavior.Restrict); + + systemBIOS.HasOne(sb => sb.BIOSModel) + .WithMany(bm => bm.SystemBIOSs) + .HasForeignKey(sb => sb.BIOSModelId) + .IsRequired() + .OnDelete(DeleteBehavior.Restrict); + + systemBIOS.HasKey(sb => new { sb.SystemId }); + + var biosModel = builder.Entity(); + + biosModel.HasOne(bm => bm.Manufacturer) + .WithMany(m => m.BIOSManufacturer) + .HasForeignKey(bm => bm.ManufacturerId) + .OnDelete(DeleteBehavior.Restrict); + + var systemOSEntity = builder.Entity(); + + systemOSEntity.HasOne(sos => sos.System) + .WithMany(s => s.SystemOSs) + .HasForeignKey(sos => sos.SystemId) + .IsRequired() + .OnDelete(DeleteBehavior.Restrict); + + systemOSEntity.HasOne(sos => sos.OperatingSystem) + .WithMany(osm => osm.SystemOSs) + .HasForeignKey(sos => sos.OperatingSystemId) + .IsRequired() + .OnDelete(DeleteBehavior.Restrict); + + systemOSEntity.HasOne(sos => sos.Architecture) + .WithMany(a => a.SystemOSs) + .HasForeignKey(sos => sos.ArchitectureId) + .OnDelete(DeleteBehavior.Restrict); + + systemOSEntity.HasKey(sc => new { sc.SystemId, sc.OperatingSystemId }); + + var systemPingData = builder.Entity(); + + systemPingData.HasOne(s => s.System) + .WithMany(s => s.SystemPingData) + .HasForeignKey(s => s.SystemId) + .OnDelete(DeleteBehavior.Restrict); + + systemPingData.HasKey(s => new { s.SystemId, s.SendPingTime }); + + var systemCPUData = builder.Entity(); + + systemCPUData.HasOne(s => s.System) + .WithMany(s => s.SystemCPUData) + .HasForeignKey(s => s.SystemId) + .OnDelete(DeleteBehavior.Restrict); + + systemCPUData.HasKey(s => new { s.SystemId, s.CollectionDateTime }); + + var systemRAMData = builder.Entity(); + + systemRAMData.HasOne(s => s.System) + .WithMany(s => s.SystemRAMData) + .HasForeignKey(s => s.SystemId) + .OnDelete(DeleteBehavior.Restrict); + + systemRAMData.HasKey(s => new { s.SystemId, s.CollectionDateTime }); + + var systemStorageData = builder.Entity(); + + systemStorageData.HasOne(s => s.System) + .WithMany(s => s.SystemStorageData) + .HasForeignKey(s => s.SystemId) + .OnDelete(DeleteBehavior.Restrict); + + systemStorageData.HasKey(s => new { s.SystemId, s.CollectionDateTime }); + + var SystemStorageData = builder.Entity(); + + SystemStorageData.HasOne(s => s.System) + .WithMany(s => s.SystemStorageData) + .HasForeignKey(s => s.SystemId) + .OnDelete(DeleteBehavior.Restrict); + + SystemStorageData.HasKey(s => new { s.SystemId, s.CollectionDateTime }); + + var systemRunningProcesses = builder.Entity(); + + systemRunningProcesses.HasOne(s => s.System) + .WithMany(s => s.SystemRunningProcesses) + .HasForeignKey(s => s.SystemId) + .OnDelete(DeleteBehavior.Restrict); + + systemRunningProcesses.HasKey(s => new { s.SystemId, s.Id, s.CollectionDateTime }); + + var systemCommands = builder.Entity(); + + systemCommands.HasOne(s => s.System) + .WithMany(s => s.SystemCommands) + .HasForeignKey(s => s.SystemId) + .OnDelete(DeleteBehavior.Restrict); + + systemCommands.HasKey(s => new { s.SystemId, s.QueuedTime }); + + } + + } +} diff --git a/syski_api/uk.co.syski.data/System.cs b/syski_api/uk.co.syski.data/System.cs new file mode 100644 index 0000000..2138a72 --- /dev/null +++ b/syski_api/uk.co.syski.data/System.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; + +namespace Syski.Data +{ + public class System + { + + public Guid Id { get; set; } + + public string HostName { get; set; } + + public Guid? ModelId { get; set; } + + public Model Model { get; set; } + + public string Secret { get; set; } + + public DateTime LastUpdated { get; set; } + + public IEnumerable Users { get; set; } + + public IEnumerable SystemTypes { get; set; } + + public IEnumerable SystemCPUs { get; set; } + + public IEnumerable SystemRAMs { get; set; } + + public IEnumerable SystemGPUs { get; set; } + + public IEnumerable SystemStorages { get; set; } + + public SystemMotherboard SystemMotherboard { get; set; } + + public SystemBIOS SystemBIOS { get; set; } + + public IEnumerable SystemOSs { get; set; } + + public IEnumerable SystemPingData { get; set; } + + public IEnumerable SystemCPUData { get; set; } + + public IEnumerable SystemRAMData { get; set; } + + public IEnumerable SystemStorageData { get; set; } + + public IEnumerable SystemRunningProcesses { get; set; } + + public IEnumerable SystemCommands { get; set; } + + } +} diff --git a/syski_api/uk.co.syski.data/SystemBIOS.cs b/syski_api/uk.co.syski.data/SystemBIOS.cs new file mode 100644 index 0000000..8796575 --- /dev/null +++ b/syski_api/uk.co.syski.data/SystemBIOS.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Syski.Data +{ + public class SystemBIOS + { + + public Guid SystemId { get; set; } + + public System System { get; set; } + + public Guid BIOSModelId { get; set; } + + public BIOSModel BIOSModel { get; set; } + + public string Caption { get; set; } + + public string Version { get; set; } + + public string Date { get; set; } + + public DateTime LastUpdated { get; set; } + + } +} diff --git a/syski_api/uk.co.syski.data/SystemCPU.cs b/syski_api/uk.co.syski.data/SystemCPU.cs new file mode 100644 index 0000000..f50a6cc --- /dev/null +++ b/syski_api/uk.co.syski.data/SystemCPU.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Syski.Data +{ + public class SystemCPU + { + + public Guid SystemId { get; set; } + + public System System { get; set; } + + public Guid CPUModelID { get; set; } + + public CPUModel CPUModel { get; set; } + + public int Slot { get; set; } + + public int ClockSpeed { get; set; } + + public int CoreCount { get; set; } + + public int ThreadCount { get; set; } + + public DateTime LastUpdated { get; set; } + + } +} diff --git a/syski_api/uk.co.syski.data/SystemCPUData.cs b/syski_api/uk.co.syski.data/SystemCPUData.cs new file mode 100644 index 0000000..3a0e2b2 --- /dev/null +++ b/syski_api/uk.co.syski.data/SystemCPUData.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Syski.Data +{ + public class SystemCPUData + { + + public Guid SystemId { get; set; } + + public System System { get; set; } + + public double Load { get; set; } + + public int Processes { get; set; } + + public DateTime CollectionDateTime { get; set; } + + } +} diff --git a/syski_api/uk.co.syski.data/SystemCommand.cs b/syski_api/uk.co.syski.data/SystemCommand.cs new file mode 100644 index 0000000..cc668a5 --- /dev/null +++ b/syski_api/uk.co.syski.data/SystemCommand.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Syski.Data +{ + public class SystemCommand + { + + public Guid SystemId { get; set; } + + public System System { get; set; } + + public string Action { get; set; } + + public string Properties { get; set; } + + public DateTime QueuedTime { get; set; } + + public DateTime? ExecutedTime { get; set; } + + } +} diff --git a/syski_api/uk.co.syski.data/SystemGPU.cs b/syski_api/uk.co.syski.data/SystemGPU.cs new file mode 100644 index 0000000..4b5408f --- /dev/null +++ b/syski_api/uk.co.syski.data/SystemGPU.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Syski.Data +{ + public class SystemGPU + { + + public Guid SystemId { get; set; } + + public System System { get; set; } + + public Guid GPUModelId { get; set; } + + public GPUModel GPUModel { get; set; } + + public int Slot { get; set; } + + public DateTime LastUpdated { get; set; } + + } +} diff --git a/syski_api/uk.co.syski.data/SystemMotherboard.cs b/syski_api/uk.co.syski.data/SystemMotherboard.cs new file mode 100644 index 0000000..902b0d7 --- /dev/null +++ b/syski_api/uk.co.syski.data/SystemMotherboard.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Syski.Data +{ + public class SystemMotherboard + { + + public Guid SystemId { get; set; } + + public System System { get; set; } + + public Guid MotherboardModelId { get; set; } + + public MotherboardModel MotherboardModel { get; set; } + + public DateTime LastUpdated { get; set; } + + } +} diff --git a/syski_api/uk.co.syski.data/SystemOS.cs b/syski_api/uk.co.syski.data/SystemOS.cs new file mode 100644 index 0000000..7493483 --- /dev/null +++ b/syski_api/uk.co.syski.data/SystemOS.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Syski.Data +{ + public class SystemOS + { + + public Guid SystemId { get; set; } + + public System System { get; set; } + + public Guid OperatingSystemId { get; set; } + + public OperatingSystemModel OperatingSystem { get; set; } + + public Guid? ArchitectureId { get; set; } + + public Architecture Architecture { get; set; } + + public string Version { get; set; } + + public DateTime LastUpdated { get; set; } + + } +} diff --git a/syski_api/uk.co.syski.data/SystemPingData.cs b/syski_api/uk.co.syski.data/SystemPingData.cs new file mode 100644 index 0000000..ff0f43b --- /dev/null +++ b/syski_api/uk.co.syski.data/SystemPingData.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Syski.Data +{ + public class SystemPingData + { + + public Guid SystemId { get; set; } + + public System System { get; set; } + + public DateTime SendPingTime { get; set; } + + public DateTime CollectionDateTime { get; set; } + + } +} diff --git a/syski_api/uk.co.syski.data/SystemRAM.cs b/syski_api/uk.co.syski.data/SystemRAM.cs new file mode 100644 index 0000000..2b573a7 --- /dev/null +++ b/syski_api/uk.co.syski.data/SystemRAM.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Syski.Data +{ + public class SystemRAM + { + + public Guid SystemId { get; set; } + + public System System { get; set; } + + public Guid RAMModelId { get; set; } + + public RAMModel RAMModel { get; set; } + + public int Slot { get; set; } + + public int Speed { get; set; } + + public DateTime LastUpdated { get; set; } + + } +} diff --git a/syski_api/uk.co.syski.data/SystemRAMData.cs b/syski_api/uk.co.syski.data/SystemRAMData.cs new file mode 100644 index 0000000..ed345f3 --- /dev/null +++ b/syski_api/uk.co.syski.data/SystemRAMData.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Syski.Data +{ + public class SystemRAMData + { + + public Guid SystemId { get; set; } + + public System System { get; set; } + + public int Free { get; set; } + + public DateTime CollectionDateTime { get; set; } + + } +} diff --git a/syski_api/uk.co.syski.data/SystemRunningProcesses.cs b/syski_api/uk.co.syski.data/SystemRunningProcesses.cs new file mode 100644 index 0000000..77a172b --- /dev/null +++ b/syski_api/uk.co.syski.data/SystemRunningProcesses.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Syski.Data +{ + public class SystemRunningProcesses + { + + public Guid SystemId { get; set; } + + public System System { get; set; } + + public int Id { get; set; } + + public string Name { get; set; } + + public long MemSize { get; set; } + + public long KernelTime { get; set; } + + public string Path { get; set; } + + public int Threads { get; set; } + + public long UpTime { get; set; } + + public int ParentId { get; set; } + + public DateTime CollectionDateTime { get; set; } + + } +} diff --git a/syski_api/uk.co.syski.data/SystemStorage.cs b/syski_api/uk.co.syski.data/SystemStorage.cs new file mode 100644 index 0000000..7e4b3e8 --- /dev/null +++ b/syski_api/uk.co.syski.data/SystemStorage.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Syski.Data +{ + public class SystemStorage + { + + public Guid SystemId { get; set; } + + public System System { get; set; } + + public Guid StorageModelId { get; set; } + + public StorageModel StorageModel { get; set; } + + public int Slot { get; set; } + + public Guid? StorageInterfaceId { get; set; } + + public StorageInterfaceType StorageInterface { get; set; } + + public DateTime LastUpdated { get; set; } + + } +} diff --git a/syski_api/uk.co.syski.data/SystemStorageData.cs b/syski_api/uk.co.syski.data/SystemStorageData.cs new file mode 100644 index 0000000..9939287 --- /dev/null +++ b/syski_api/uk.co.syski.data/SystemStorageData.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Syski.Data +{ + public class SystemStorageData + { + + public Guid SystemId { get; set; } + + public System System { get; set; } + + public float Time { get; set; } + + public float Transfers { get; set; } + + public float Reads { get; set; } + + public float Writes { get; set; } + + public float ByteReads { get; set; } + + public float ByteWrites { get; set; } + + public DateTime CollectionDateTime { get; set; } + + } +} diff --git a/syski_api/uk.co.syski.data/SystemType.cs b/syski_api/uk.co.syski.data/SystemType.cs new file mode 100644 index 0000000..5e0606f --- /dev/null +++ b/syski_api/uk.co.syski.data/SystemType.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Syski.Data +{ + public class SystemType + { + + public Guid SystemId { get; set; } + + public System System { get; set; } + + public Guid TypeId { get; set; } + + public SystemTypeName Type { get; set; } + + } +} diff --git a/syski_api/uk.co.syski.data/SystemTypeName.cs b/syski_api/uk.co.syski.data/SystemTypeName.cs new file mode 100644 index 0000000..fb284d7 --- /dev/null +++ b/syski_api/uk.co.syski.data/SystemTypeName.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Syski.Data +{ + public class SystemTypeName + { + + public Guid Id { get; set; } + + public string Name { get; set; } + + public IEnumerable SystemTypes { get; set; } + + } +} diff --git a/syski_api/uk.co.syski.websocket/Program.cs b/syski_api/uk.co.syski.websocket/Program.cs new file mode 100644 index 0000000..4162757 --- /dev/null +++ b/syski_api/uk.co.syski.websocket/Program.cs @@ -0,0 +1,17 @@ +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; + +namespace Syski.WebSocket +{ + public class Program + { + public static void Main(string[] args) + { + CreateWebHostBuilder(args).Build().Run(); + } + + public static IWebHostBuilder CreateWebHostBuilder(string[] args) => + WebHost.CreateDefaultBuilder(args) + .UseStartup(); + } +} diff --git a/syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Action.cs b/syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Action.cs new file mode 100644 index 0000000..e98fd1a --- /dev/null +++ b/syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Action.cs @@ -0,0 +1,13 @@ +using Newtonsoft.Json.Linq; + +namespace Syski.WebSocket.Services.WebSockets.Actions +{ + public class Action + { + + public string action { get; set; } + + public JObject properties { get; set; } + + } +} diff --git a/syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/ActionFactory.cs b/syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/ActionFactory.cs new file mode 100644 index 0000000..1d8247e --- /dev/null +++ b/syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/ActionFactory.cs @@ -0,0 +1,143 @@ +using Newtonsoft.Json.Linq; +using Syski.WebSocket.Services.WebSockets.Actions.Handlers; +using System; + +namespace Syski.WebSocket.Services.WebSockets.Actions +{ + public class ActionFactory + { + + public static ActionHandler CreateActionHandler(Action action, WebSocketConnection webSocketConnection, IServiceProvider serviceProvider) + { + ActionHandler result = null; + switch (action.action) + { + case "user-authentication": + { + result = new UserAuthenticationHandler(action, webSocketConnection, serviceProvider); + break; + } + case "system-authentication": + { + result = new SystemAuthenticationHandler(action, webSocketConnection, serviceProvider); + break; + } + case "staticsystem": + { + result = new StaticSystemHandler(action, webSocketConnection, serviceProvider); + break; + } + case "staticcpu": + { + result = new StaticCPUHandler(action, webSocketConnection, serviceProvider); + break; + } + case "staticram": + { + result = new StaticRAMHandler(action, webSocketConnection, serviceProvider); + break; + } + case "staticgpu": + { + result = new StaticGPUHandler(action, webSocketConnection, serviceProvider); + break; + } + case "staticstorage": + { + result = new StaticStorageHandler(action, webSocketConnection, serviceProvider); + break; + } + case "staticmotherboard": + { + result = new StaticMotherboardHandler(action, webSocketConnection, serviceProvider); + break; + } + case "staticbios": + { + result = new StaticBIOSHandler(action, webSocketConnection, serviceProvider); + break; + } + case "staticos": + { + result = new StaticOperatingSystemHandler(action, webSocketConnection, serviceProvider); + break; + } + case "variableping": + { + result = new VariablePingHandler(action, webSocketConnection, serviceProvider); + break; + } + case "variablecpu": + { + result = new VariableCPUHandler(action, webSocketConnection, serviceProvider); + break; + } + case "variableram": + { + result = new VariableRAMHandler(action, webSocketConnection, serviceProvider); + break; + } + case "variablestorage": + { + result = new VariableStorageHandler(action, webSocketConnection, serviceProvider); + break; + } + case "runningprocesses": + { + result = new VariableRunningProcessesHandler(action, webSocketConnection, serviceProvider); + break; + } + default: + { + result = new DefaultHandler(action, webSocketConnection, serviceProvider); + break; + } + } + return result; + } + + public static ActionHandler CreateAuthActionHandler(Action action, WebSocketConnection webSocketConnection, IServiceProvider serviceProvider) + { + ActionHandler result = null; + switch (action.action) + { + case "user-authentication": + { + result = new UserAuthenticationHandler(action, webSocketConnection, serviceProvider); + break; + } + case "system-authentication": + { + result = new SystemAuthenticationHandler(action, webSocketConnection, serviceProvider); + break; + } + default: + { + result = new DefaultHandler(action, webSocketConnection, serviceProvider); + break; + } + } + return result; + } + + + public static Action CreateAction(string actionName) + { + return CreateAction(actionName, null); + } + + public static Action CreateAction(string actionName, JObject actionProperties) + { + if (actionProperties == null) + { + actionProperties = new JObject(); + } + return new Action() + { + action = actionName, + properties = actionProperties + }; + } + + } +} diff --git a/syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Handlers/ActionHandler.cs b/syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Handlers/ActionHandler.cs new file mode 100644 index 0000000..76d96fb --- /dev/null +++ b/syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Handlers/ActionHandler.cs @@ -0,0 +1,22 @@ +using System; + +namespace Syski.WebSocket.Services.WebSockets.Actions.Handlers +{ + public abstract class ActionHandler + { + + protected readonly Action action; + protected readonly WebSocketConnection webSocketConnection; + protected readonly IServiceProvider serviceProvider; + + public ActionHandler(Action action, WebSocketConnection webSocketConnection, IServiceProvider serviceProvider) + { + this.action = action; + this.webSocketConnection = webSocketConnection; + this.serviceProvider = serviceProvider; + } + + public abstract void HandleAction(); + + } +} diff --git a/syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Handlers/AuthenticationHandler.cs b/syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Handlers/AuthenticationHandler.cs new file mode 100644 index 0000000..60ce194 --- /dev/null +++ b/syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Handlers/AuthenticationHandler.cs @@ -0,0 +1,42 @@ +using System; +using System.Threading; +using Microsoft.Extensions.DependencyInjection; + +namespace Syski.WebSocket.Services.WebSockets.Actions.Handlers +{ + public class AuthenticationHandler : ActionHandler + { + + // Delay between each command run after authentication + private static readonly int delayBetweenAction = 100; + + // Commands run after authentication + private static readonly string[] actions = { + "staticsystem", + "staticcpu", + "staticram", + "staticgpu", + "staticstorage", + "staticmotherboard", + "staticbios", + "staticos" + }; + + public AuthenticationHandler(Action action, WebSocketConnection webSocketConnection, IServiceProvider serviceProvider) : base(action, webSocketConnection, serviceProvider) + { + } + + public override async void HandleAction() + { + webSocketConnection.Authentication = true; + WebSocketManager websocketManager = serviceProvider.GetService(); + websocketManager.AddSocket(webSocketConnection.Id, webSocketConnection); + foreach (string action in actions) + { + await webSocketConnection.SendAction(action); + Thread.Sleep(delayBetweenAction); + } + } + + } +} diff --git a/syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Handlers/DefaultHandler.cs b/syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Handlers/DefaultHandler.cs new file mode 100644 index 0000000..aec31d0 --- /dev/null +++ b/syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Handlers/DefaultHandler.cs @@ -0,0 +1,23 @@ +using Newtonsoft.Json.Linq; +using System; + +namespace Syski.WebSocket.Services.WebSockets.Actions.Handlers +{ + public class DefaultHandler : ActionHandler + { + + public DefaultHandler(Action action, WebSocketConnection webSocket, IServiceProvider serviceProvider) : base(action, webSocket, serviceProvider) + { + } + + public override async void HandleAction() + { + JObject properties = new JObject + { + { "message", "Invalid Action Sent (" + action.action + ")" } + }; + await webSocketConnection.SendAction("error", properties); + } + + } +} diff --git a/syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Handlers/StaticBIOSHandler.cs b/syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Handlers/StaticBIOSHandler.cs new file mode 100644 index 0000000..c5b94fb --- /dev/null +++ b/syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Handlers/StaticBIOSHandler.cs @@ -0,0 +1,99 @@ +using Microsoft.Extensions.DependencyInjection; +using Syski.Data; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Syski.WebSocket.Services.WebSockets.Actions.Handlers +{ + public class StaticBIOSHandler : ActionHandler + { + + public StaticBIOSHandler(Action action, WebSocketConnection webSocketConnection, IServiceProvider serviceProvider) : base(action, webSocketConnection, serviceProvider) + { + } + + public override void HandleAction() + { + using (var serviceScope = serviceProvider.GetRequiredService().CreateScope()) + { + var context = serviceScope.ServiceProvider.GetService(); + var systemUUID = serviceProvider.GetService().GetSystemId(webSocketConnection.Id); + + string manufacturerFromJSON = (string) action.properties.SelectToken("manufacturer"); + string captionFromJSON = (string)action.properties.SelectToken("caption"); + string versionFromJSON = (string) action.properties.SelectToken("version"); + string dateFromJSON = (string) action.properties.SelectToken("date"); + + Manufacturer manufacturer = context.Manufacturers.Where(m => m.Name == manufacturerFromJSON).FirstOrDefault(); + if (manufacturer == null && manufacturerFromJSON != null) + { + manufacturer = new Manufacturer + { + Name = manufacturerFromJSON + }; + context.Add(manufacturer); + context.SaveChanges(); + } + + Guid? manufacturerId = (manufacturer != null ? manufacturer.Id : (Guid?) null); + var biosModel = context.BIOSModels.FirstOrDefault(m => m.ManufacturerId.Equals(manufacturer)); + if (biosModel == null) + { + biosModel = new BIOSModel + { + ManufacturerId = manufacturerId + }; + context.Add(biosModel); + context.SaveChanges(); + } + + var systemBIOS = context.SystemBIOSs.FirstOrDefault(m => m.SystemId.Equals(systemUUID)); + if (systemBIOS == null) + { + systemBIOS = new SystemBIOS + { + SystemId = systemUUID, + BIOSModelId = biosModel.Id, + Caption = captionFromJSON, + Version = versionFromJSON, + Date = dateFromJSON, + LastUpdated = DateTime.Now + }; + context.Add(systemBIOS); + context.SaveChanges(); + } + else + { + if (!systemBIOS.BIOSModelId.Equals(biosModel.Id)) + { + context.Remove(systemBIOS); + systemBIOS = new SystemBIOS + { + SystemId = systemUUID, + BIOSModelId = biosModel.Id, + Caption = captionFromJSON, + Version = versionFromJSON, + Date = dateFromJSON, + LastUpdated = DateTime.Now + }; + context.Add(systemBIOS); + context.SaveChanges(); + } + else + { + systemBIOS.Caption = captionFromJSON; + systemBIOS.Version = versionFromJSON; + systemBIOS.Date = dateFromJSON; + systemBIOS.LastUpdated = DateTime.Now; + context.Update(systemBIOS); + context.SaveChanges(); + } + } + + } + } + + } +} diff --git a/syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Handlers/StaticCPUHandler.cs b/syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Handlers/StaticCPUHandler.cs new file mode 100644 index 0000000..74ac392 --- /dev/null +++ b/syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Handlers/StaticCPUHandler.cs @@ -0,0 +1,131 @@ +using Microsoft.Extensions.DependencyInjection; +using Syski.Data; +using System; +using System.Linq; + +namespace Syski.WebSocket.Services.WebSockets.Actions.Handlers +{ + public class StaticCPUHandler : ActionHandler + { + + public StaticCPUHandler(Action action, WebSocketConnection webSocketConnection, IServiceProvider serviceProvider) : base(action, webSocketConnection, serviceProvider) + { + } + + public override void HandleAction() + { + using (var serviceScope = serviceProvider.GetRequiredService().CreateScope()) + { + var context = serviceScope.ServiceProvider.GetService(); + var systemUUID = serviceProvider.GetService().GetSystemId(webSocketConnection.Id); + DateTime lastUpdated = DateTime.Now; + int slot = 0; + + string modelFromJSON = (string) action.properties.SelectToken("model"); + string manufacturerFromJSON = (string) action.properties.SelectToken("manufacturer"); + string architectureFromJSON = (string) action.properties.SelectToken("architecture"); + int clockspeedFromJSON = (int) action.properties.SelectToken("clockspeed"); + int corecountFromJSON = (int) action.properties.SelectToken("corecount"); + int threadcountFromJSON = (int) action.properties.SelectToken("threadcount"); + + Manufacturer manufacturer = context.Manufacturers.Where(m => m.Name == manufacturerFromJSON).FirstOrDefault(); + if (manufacturer == null && manufacturerFromJSON != null) + { + manufacturer = new Manufacturer + { + Name = manufacturerFromJSON + }; + context.Add(manufacturer); + context.SaveChanges(); + } + + Guid? manufacturerId = (manufacturer != null ? manufacturer.Id : (Guid?) null); + Model model = context.Models.FirstOrDefault(m => m.Name.Equals(modelFromJSON) && m.ManufacturerId.Equals(manufacturerId)); + if ((model == null) && ((modelFromJSON != null && manufacturer == null) || (modelFromJSON != null || manufacturer != null))) + { + model = new Model + { + Name = modelFromJSON + }; + if (manufacturer != null) + { + model.ManufacturerId = manufacturer.Id; + } + context.Add(model); + context.SaveChanges(); + } + + var architecture = context.Architectures.FirstOrDefault(m => m.Name.Equals(architectureFromJSON)); + if (architecture == null && architectureFromJSON != null) + { + architecture = new Architecture + { + Name = architectureFromJSON + }; + context.Add(architecture); + context.SaveChanges(); + } + + Guid? modelId = (model != null ? model.Id : (Guid?) null); + Guid? architectureId = (architecture != null ? architecture.Id : (Guid?) null); + var cpuModel = context.CPUModels.FirstOrDefault(m => m.ModelId.Equals(modelId) && m.ArchitectureId.Equals(architectureId)); + if (cpuModel == null) + { + cpuModel = new CPUModel + { + ModelId = modelId, + ArchitectureId = architectureId + }; + context.Add(cpuModel); + context.SaveChanges(); + } + + var systemCPU = context.SystemCPUs.Where(m => m.SystemId.Equals(systemUUID) && m.Slot.Equals(slot)).FirstOrDefault(); + if (systemCPU == null) + { + systemCPU = new SystemCPU + { + SystemId = systemUUID, + CPUModelID = cpuModel.Id, + ClockSpeed = clockspeedFromJSON, + CoreCount = corecountFromJSON, + ThreadCount = threadcountFromJSON, + LastUpdated = lastUpdated + }; + context.Add(systemCPU); + context.SaveChanges(); + } + else + { + if (!systemCPU.CPUModelID.Equals(cpuModel.Id)) + { + context.Remove(systemCPU); + systemCPU = new SystemCPU + { + SystemId = systemUUID, + CPUModelID = cpuModel.Id, + ClockSpeed = clockspeedFromJSON, + CoreCount = corecountFromJSON, + ThreadCount = threadcountFromJSON, + LastUpdated = lastUpdated + }; + context.Add(systemCPU); + context.SaveChanges(); + } + else + { + systemCPU.ClockSpeed = clockspeedFromJSON; + systemCPU.CoreCount = corecountFromJSON; + systemCPU.ThreadCount = threadcountFromJSON; + systemCPU.LastUpdated = lastUpdated; + context.Update(systemCPU); + context.SaveChanges(); + } + } + + //slot++; + } + } + + } +} diff --git a/syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Handlers/StaticGPUHandler.cs b/syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Handlers/StaticGPUHandler.cs new file mode 100644 index 0000000..a803216 --- /dev/null +++ b/syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Handlers/StaticGPUHandler.cs @@ -0,0 +1,110 @@ +using Microsoft.Extensions.DependencyInjection; +using Syski.Data; +using Syski.WebSocket.Services.WebSockets.Actions.Handlers; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Syski.WebSocket.Services.WebSockets.Actions.Handlers +{ + public class StaticGPUHandler : ActionHandler + { + + public StaticGPUHandler(Action action, WebSocketConnection webSocketConnection, IServiceProvider serviceProvider) : base(action, webSocketConnection, serviceProvider) + { + } + + public override void HandleAction() + { + using (var serviceScope = serviceProvider.GetRequiredService().CreateScope()) + { + var context = serviceScope.ServiceProvider.GetService(); + var systemUUID = serviceProvider.GetService().GetSystemId(webSocketConnection.Id); + DateTime lastUpdated = DateTime.Now; + int slot = 0; + + string modelFromJSON = (string) action.properties.SelectToken("model"); + string manufacturerFromJSON = (string) action.properties.SelectToken("manufacturer"); + + Manufacturer manufacturer = context.Manufacturers.Where(m => m.Name == manufacturerFromJSON).FirstOrDefault(); + if (manufacturer == null && manufacturerFromJSON != null) + { + manufacturer = new Manufacturer + { + Name = manufacturerFromJSON + }; + context.Add(manufacturer); + context.SaveChanges(); + } + + Guid? manufacturerId = (manufacturer != null ? manufacturer.Id : (Guid?)null); + Model model = context.Models.FirstOrDefault(m => m.Name.Equals(modelFromJSON) && m.ManufacturerId.Equals(manufacturerId)); + if ((model == null) && ((modelFromJSON != null && manufacturer == null) || (modelFromJSON != null || manufacturer != null))) + { + model = new Model + { + Name = modelFromJSON + }; + if (manufacturer != null) + { + model.ManufacturerId = manufacturer.Id; + } + context.Add(model); + context.SaveChanges(); + } + + Guid? modelId = (model != null ? model.Id : (Guid?)null); + var gpuModel = context.GPUModels.FirstOrDefault(m => m.ModelId.Equals(modelId)); + if (gpuModel == null) + { + gpuModel = new GPUModel + { + ModelId = modelId, + }; + context.Add(gpuModel); + context.SaveChanges(); + } + + var systemGPU = context.SystemGPUs.Where(m => m.SystemId.Equals(systemUUID) && m.Slot.Equals(slot)).FirstOrDefault(); + if (systemGPU == null) + { + systemGPU = new SystemGPU + { + SystemId = systemUUID, + GPUModelId = gpuModel.Id, + Slot = slot, + LastUpdated = lastUpdated + }; + context.Add(systemGPU); + context.SaveChanges(); + } + else + { + if (!systemGPU.GPUModelId.Equals(gpuModel.Id)) + { + context.Remove(systemGPU); + systemGPU = new SystemGPU + { + SystemId = systemUUID, + GPUModelId = gpuModel.Id, + Slot = slot, + LastUpdated = lastUpdated + }; + context.Add(systemGPU); + context.SaveChanges(); + } + else + { + systemGPU.LastUpdated = lastUpdated; + context.Update(systemGPU); + context.SaveChanges(); + } + } + + //slot++; + } + } + + } +} diff --git a/syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Handlers/StaticMotherboardHandler.cs b/syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Handlers/StaticMotherboardHandler.cs new file mode 100644 index 0000000..e89efd7 --- /dev/null +++ b/syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Handlers/StaticMotherboardHandler.cs @@ -0,0 +1,106 @@ +using Microsoft.Extensions.DependencyInjection; +using Syski.Data; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Syski.WebSocket.Services.WebSockets.Actions.Handlers +{ + public class StaticMotherboardHandler : ActionHandler + { + + public StaticMotherboardHandler(Action action, WebSocketConnection webSocketConnection, IServiceProvider serviceProvider) : base(action, webSocketConnection, serviceProvider) + { + } + + public override void HandleAction() + { + using (var serviceScope = serviceProvider.GetRequiredService().CreateScope()) + { + var context = serviceScope.ServiceProvider.GetService(); + var systemUUID = serviceProvider.GetService().GetSystemId(webSocketConnection.Id); + + string modelFromJSON = (string) action.properties.SelectToken("model"); + string manufacturerFromJSON = (string) action.properties.SelectToken("manufacturer"); + string versionFromJSON = (string) action.properties.SelectToken("version"); + + Manufacturer manufacturer = context.Manufacturers.Where(m => m.Name == manufacturerFromJSON).FirstOrDefault(); + if (manufacturer == null && manufacturerFromJSON != null) + { + manufacturer = new Manufacturer + { + Name = manufacturerFromJSON + }; + context.Add(manufacturer); + context.SaveChanges(); + } + + Guid? manufacturerId = (manufacturer != null ? manufacturer.Id : (Guid?) null); + Model model = context.Models.FirstOrDefault(m => m.Name.Equals(modelFromJSON) && m.ManufacturerId.Equals(manufacturerId)); + if ((model == null) && ((modelFromJSON != null && manufacturer == null) || (modelFromJSON != null || manufacturer != null))) + { + model = new Model + { + Name = modelFromJSON + }; + if (manufacturer != null) + { + model.ManufacturerId = manufacturer.Id; + } + context.Add(model); + context.SaveChanges(); + } + + Guid? modelId = (model != null ? model.Id : (Guid?) null); + var motherboardModel = context.MotherboardModels.FirstOrDefault(m => m.ModelId.Equals(modelId) && m.Version.Equals(versionFromJSON)); + if (motherboardModel == null) + { + motherboardModel = new MotherboardModel + { + ModelId = modelId, + Version = versionFromJSON + }; + context.Add(motherboardModel); + context.SaveChanges(); + } + + var systemMotherboard = context.SystemMotherboards.FirstOrDefault(m => m.SystemId.Equals(systemUUID)); + if (systemMotherboard == null) + { + systemMotherboard = new SystemMotherboard + { + SystemId = systemUUID, + MotherboardModelId = motherboardModel.Id, + LastUpdated = DateTime.Now + }; + context.Add(systemMotherboard); + context.SaveChanges(); + } + else + { + if (!systemMotherboard.MotherboardModelId.Equals(motherboardModel.Id)) + { + context.Remove(systemMotherboard); + systemMotherboard = new SystemMotherboard + { + SystemId = systemUUID, + MotherboardModelId = motherboardModel.Id, + LastUpdated = DateTime.Now + }; + context.Add(systemMotherboard); + context.SaveChanges(); + } + else + { + systemMotherboard.LastUpdated = DateTime.Now; + context.Update(systemMotherboard); + context.SaveChanges(); + } + } + + } + } + + } +} diff --git a/syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Handlers/StaticOperatingSystemHandler.cs b/syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Handlers/StaticOperatingSystemHandler.cs new file mode 100644 index 0000000..a0ae492 --- /dev/null +++ b/syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Handlers/StaticOperatingSystemHandler.cs @@ -0,0 +1,95 @@ +using Microsoft.Extensions.DependencyInjection; +using Syski.Data; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Syski.WebSocket.Services.WebSockets.Actions.Handlers +{ + public class StaticOperatingSystemHandler : ActionHandler + { + + public StaticOperatingSystemHandler(Action action, WebSocketConnection webSocketConnection, IServiceProvider serviceProvider) : base(action, webSocketConnection, serviceProvider) + { + } + + public override void HandleAction() + { + using (var serviceScope = serviceProvider.GetRequiredService().CreateScope()) + { + var context = serviceScope.ServiceProvider.GetService(); + var systemUUID = serviceProvider.GetService().GetSystemId(webSocketConnection.Id); + + string osnameFromJSON = (string) action.properties.SelectToken("name"); + string architectureFromJSON = (string) action.properties.SelectToken("architecture"); + string versionFromJSON = (string) action.properties.SelectToken("version"); + + var osName = context.OperatingSystemModels.FirstOrDefault(m => m.Name == osnameFromJSON); + if (osName == null) + { + osName = new Data.OperatingSystemModel() + { + Name = osnameFromJSON + }; + context.Add(osName); + context.SaveChanges(); + } + + var architecture = context.Architectures.Where(m => m.Name == architectureFromJSON).FirstOrDefault(); + if (architecture == null && architectureFromJSON != null) + { + architecture = new Architecture + { + Name = architectureFromJSON + }; + context.Add(architecture); + context.SaveChanges(); + } + + Guid? architectureId = (architecture != null ? architecture.Id : (Guid?)null); + var systemOS = context.SystemOSs.FirstOrDefault(m => m.SystemId.Equals(systemUUID)); + if (systemOS == null) + { + systemOS = new SystemOS() + { + SystemId = systemUUID, + OperatingSystemId = osName.Id, + ArchitectureId = architectureId, + Version = versionFromJSON, + LastUpdated = DateTime.Now + }; + context.Add(systemOS); + context.SaveChanges(); + } + else + { + if (!systemOS.OperatingSystemId.Equals(osName.Id)) + { + context.Remove(systemOS); + systemOS = new SystemOS() + { + SystemId = systemUUID, + OperatingSystemId = osName.Id, + ArchitectureId = architectureId, + Version = versionFromJSON, + LastUpdated = DateTime.Now + }; + context.Add(systemOS); + context.SaveChanges(); + } + else + { + systemOS.ArchitectureId = architectureId; + systemOS.Version = versionFromJSON; + systemOS.LastUpdated = DateTime.Now; + context.Update(systemOS); + context.SaveChanges(); + } + } + + } + } + + } +} diff --git a/syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Handlers/StaticRAMHandler.cs b/syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Handlers/StaticRAMHandler.cs new file mode 100644 index 0000000..d1e040d --- /dev/null +++ b/syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Handlers/StaticRAMHandler.cs @@ -0,0 +1,120 @@ +using Microsoft.Extensions.DependencyInjection; +using Newtonsoft.Json.Linq; +using Syski.Data; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Syski.WebSocket.Services.WebSockets.Actions.Handlers +{ + public class StaticRAMHandler : ActionHandler + { + public StaticRAMHandler(Action action, WebSocketConnection webSocketConnection, IServiceProvider serviceProvider) : base(action, webSocketConnection, serviceProvider) + { + } + + public override void HandleAction() + { + using (var serviceScope = serviceProvider.GetRequiredService().CreateScope()) + { + var context = serviceScope.ServiceProvider.GetService(); + var systemUUID = serviceProvider.GetService().GetSystemId(webSocketConnection.Id); + DateTime lastUpdated = DateTime.Now; + int slot = 0; + + JArray ramArray = (JArray) action.properties.SelectToken("ram"); + foreach (JToken ram in ramArray) + { + string modelFromJSON = (string) ram.SelectToken("model"); + string manufacturerFromJSON = (string) ram.SelectToken("manufacturer"); + string typeFromJSON = (string) ram.SelectToken("type"); + int speedFromJSON = (int) ram.SelectToken("speed"); + long sizeFromJSON = (long) ram.SelectToken("size"); + + Manufacturer manufacturer = context.Manufacturers.Where(m => m.Name == manufacturerFromJSON).FirstOrDefault(); + if (manufacturer == null && manufacturerFromJSON != null) + { + manufacturer = new Manufacturer + { + Name = manufacturerFromJSON + }; + context.Add(manufacturer); + context.SaveChanges(); + } + + Guid? manufacturerId = (manufacturer != null ? manufacturer.Id : (Guid?) null); + Model model = context.Models.FirstOrDefault(m => m.Name.Equals(modelFromJSON) && m.ManufacturerId.Equals(manufacturerId)); + if ((model == null) && ((modelFromJSON != null && manufacturer == null) || (modelFromJSON != null || manufacturer != null))) + { + model = new Model + { + Name = modelFromJSON + }; + if (manufacturer != null) + { + model.ManufacturerId = manufacturer.Id; + } + context.Add(model); + context.SaveChanges(); + } + + Guid? modelId = (model != null ? model.Id : (Guid?) null); + var ramModel = context.RAMModels.FirstOrDefault(m => m.ModelId.Equals(modelId) && m.Size.Equals(sizeFromJSON)); + if (ramModel == null) + { + ramModel = new RAMModel + { + ModelId = modelId, + Size = sizeFromJSON + }; + context.Add(ramModel); + context.SaveChanges(); + } + + var systemRAM = context.SystemRAMs.Where(m => m.SystemId.Equals(systemUUID) && m.Slot.Equals(slot)).FirstOrDefault(); + if (systemRAM == null) + { + systemRAM = new SystemRAM + { + SystemId = systemUUID, + RAMModelId = ramModel.Id, + Slot = slot, + Speed = speedFromJSON, + LastUpdated = lastUpdated + }; + context.Add(systemRAM); + context.SaveChanges(); + } + else + { + if (!systemRAM.RAMModelId.Equals(ramModel.Id)) + { + context.Remove(systemRAM); + systemRAM = new SystemRAM + { + SystemId = systemUUID, + RAMModelId = ramModel.Id, + Slot = slot, + Speed = speedFromJSON, + LastUpdated = lastUpdated + }; + context.Add(systemRAM); + context.SaveChanges(); + } + else + { + systemRAM.Speed = speedFromJSON; + systemRAM.LastUpdated = lastUpdated; + context.Update(systemRAM); + context.SaveChanges(); + } + } + slot++; + } + } + + } + + } +} diff --git a/syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Handlers/StaticStorageHandler.cs b/syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Handlers/StaticStorageHandler.cs new file mode 100644 index 0000000..32091c7 --- /dev/null +++ b/syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Handlers/StaticStorageHandler.cs @@ -0,0 +1,132 @@ +using Microsoft.Extensions.DependencyInjection; +using Newtonsoft.Json.Linq; +using Syski.Data; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Syski.WebSocket.Services.WebSockets.Actions.Handlers +{ + public class StaticStorageHandler : ActionHandler + { + + public StaticStorageHandler(Action action, WebSocketConnection webSocketConnection, IServiceProvider serviceProvider) : base(action, webSocketConnection, serviceProvider) + { + } + + public override void HandleAction() + { + using (var serviceScope = serviceProvider.GetRequiredService().CreateScope()) + { + var context = serviceScope.ServiceProvider.GetService(); + var systemUUID = serviceProvider.GetService().GetSystemId(webSocketConnection.Id); + DateTime lastUpdated = DateTime.Now; + int slot = 0; + JArray storageArray = (JArray)action.properties.SelectToken("storage"); + + foreach (JToken storage in storageArray) + { + string modelFromJSON = (string) storage.SelectToken("model"); + string manufacturerFromJSON = (string) storage.SelectToken("manufacturer"); + string interfaceFromJSON = (string) storage.SelectToken("interface"); + long sizeFromJSON = (long) storage.SelectToken("size"); + + Manufacturer manufacturer = context.Manufacturers.Where(m => m.Name == manufacturerFromJSON).FirstOrDefault(); + if (manufacturer == null && manufacturerFromJSON != null) + { + manufacturer = new Manufacturer + { + Name = manufacturerFromJSON + }; + context.Add(manufacturer); + context.SaveChanges(); + } + + Guid? manufacturerId = (manufacturer != null ? manufacturer.Id : (Guid?) null); + Model model = context.Models.FirstOrDefault(m => m.Name.Equals(modelFromJSON) && m.ManufacturerId.Equals(manufacturerId)); + if ((model == null) && ((modelFromJSON != null && manufacturer == null) || (modelFromJSON != null || manufacturer != null))) + { + model = new Model + { + Name = modelFromJSON + }; + if (manufacturer != null) + { + model.ManufacturerId = manufacturer.Id; + } + context.Add(model); + context.SaveChanges(); + } + + Guid? modelId = (model != null ? model.Id : (Guid?) null); + var storageModel = context.StorageModels.FirstOrDefault(m => m.ModelId.Equals(modelId) && m.Size.Equals(sizeFromJSON)); + if (storageModel == null) + { + storageModel = new StorageModel + { + ModelId = modelId, + Size = sizeFromJSON + }; + context.Add(storageModel); + context.SaveChanges(); + } + + var interfacetype = context.StorageInterfaceTypes.Where(t => t.Name == interfaceFromJSON).FirstOrDefault(); + if (interfacetype == null && interfaceFromJSON != null) + { + interfacetype = new StorageInterfaceType() + { + Name = interfaceFromJSON + }; + context.Add(interfacetype); + context.SaveChanges(); + } + + Guid? interfacetypeId = (interfacetype != null ? interfacetype.Id : (Guid?) null); + var systemStorage = context.SystemStorages.Where(m => m.SystemId.Equals(systemUUID) && m.Slot.Equals(slot)).FirstOrDefault(); + if (systemStorage == null) + { + systemStorage = new SystemStorage + { + SystemId = systemUUID, + StorageModelId = storageModel.Id, + Slot = slot, + StorageInterfaceId = interfacetypeId, + LastUpdated = lastUpdated + }; + context.Add(systemStorage); + context.SaveChanges(); + } + else + { + if (!systemStorage.StorageModelId.Equals(storageModel.Id)) + { + context.Remove(systemStorage); + systemStorage = new SystemStorage + { + SystemId = systemUUID, + StorageModelId = storageModel.Id, + Slot = slot, + StorageInterfaceId = interfacetypeId, + LastUpdated = lastUpdated + }; + context.Add(systemStorage); + context.SaveChanges(); + } + else + { + systemStorage.StorageInterfaceId = interfacetypeId; + systemStorage.LastUpdated = lastUpdated; + context.Update(systemStorage); + context.SaveChanges(); + } + } + slot++; + } + } + + } + + } +} diff --git a/syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Handlers/StaticSystemHandler.cs b/syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Handlers/StaticSystemHandler.cs new file mode 100644 index 0000000..888e39d --- /dev/null +++ b/syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Handlers/StaticSystemHandler.cs @@ -0,0 +1,125 @@ +using Microsoft.Extensions.DependencyInjection; +using Syski.Data; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Syski.WebSocket.Services.WebSockets.Actions.Handlers +{ + public class StaticSystemHandler : ActionHandler + { + + public StaticSystemHandler(Action action, WebSocketConnection webSocketConnection, IServiceProvider serviceProvider) : base(action, webSocketConnection, serviceProvider) + { + } + + public override void HandleAction() + { + using (var serviceScope = serviceProvider.GetRequiredService().CreateScope()) + { + var context = serviceScope.ServiceProvider.GetService(); + var systemUUID = serviceProvider.GetService().GetSystemId(webSocketConnection.Id); + + var system = context.Systems.Where(u => u.Id == systemUUID).FirstOrDefault(); + if (system != null) + { + string modelFromJSON = (string) action.properties.SelectToken("model"); + string manufacturerFromJSON = (string) action.properties.SelectToken("manufacturer"); + string typeFromJSON = (string) action.properties.SelectToken("type"); + string hostNameFromJSON = (string) action.properties.SelectToken("hostname"); + + Manufacturer manufacturer = context.Manufacturers.Where(m => m.Name == manufacturerFromJSON).FirstOrDefault(); + if (manufacturer == null && manufacturerFromJSON != null) + { + manufacturer = new Manufacturer + { + Name = manufacturerFromJSON + }; + context.Add(manufacturer); + context.SaveChanges(); + } + + Guid? manufacturerId = (manufacturer != null ? manufacturer.Id : (Guid?) null); + Model model = context.Models.FirstOrDefault(m => m.Name.Equals(modelFromJSON) && m.ManufacturerId.Equals(manufacturerId)); + if ((model == null) && ((modelFromJSON != null && manufacturer == null) || (modelFromJSON != null || manufacturer != null))) + { + model = new Model + { + Name = modelFromJSON + }; + if (manufacturer != null) + { + model.ManufacturerId = manufacturer.Id; + } + context.Add(model); + context.SaveChanges(); + } + + List systemTypeNameList = new List(); + SystemTypeName systemTypeName = context.SystemTypeNames.FirstOrDefault(stn => stn.Name.Equals(typeFromJSON)); + if (systemTypeName == null && typeFromJSON != null) + { + systemTypeName = new SystemTypeName + { + Name = typeFromJSON + }; + context.Add(systemTypeName); + context.SaveChanges(); + systemTypeNameList.Add(systemTypeName); + } + else + { + systemTypeNameList.Add(systemTypeName); + } + + bool changed = false; + List currentSystemTypes = context.SystemTypes.Where(st => st.SystemId.Equals(systemUUID)).ToList(); + List removeSystemTypes = currentSystemTypes.ToList(); + foreach (SystemTypeName stn in systemTypeNameList) + { + bool found = false; + foreach(SystemType st in currentSystemTypes) + { + if (st.TypeId.Equals(stn.Id)) + { + found = true; + removeSystemTypes.Remove(st); + } + } + + if (!found) + { + SystemType systemType = new SystemType + { + SystemId = systemUUID, + TypeId = stn.Id + }; + context.Add(systemType); + changed = true; + } + } + if (changed) + { + context.SaveChanges(); + } + + if (removeSystemTypes.Count > 0) + { + foreach (SystemType st in removeSystemTypes) + { + context.Remove(st); + } + context.SaveChanges(); + } + + system.ModelId = (model != null ? model.Id : (Guid?) null); + system.HostName = hostNameFromJSON; + system.LastUpdated = DateTime.Now; + context.Update(system); + context.SaveChanges(); + } + } + } + + } +} diff --git a/syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Handlers/SystemAuthenticationHandler.cs b/syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Handlers/SystemAuthenticationHandler.cs new file mode 100644 index 0000000..8ee9c66 --- /dev/null +++ b/syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Handlers/SystemAuthenticationHandler.cs @@ -0,0 +1,31 @@ +using Microsoft.Extensions.DependencyInjection; +using Syski.Data; +using System; +using System.Linq; + +namespace Syski.WebSocket.Services.WebSockets.Actions.Handlers +{ + public class SystemAuthenticationHandler : AuthenticationHandler + { + + public SystemAuthenticationHandler(Action action, WebSocketConnection webSocketConnection, IServiceProvider serviceProvider) : base(action, webSocketConnection, serviceProvider) + { + } + + public override void HandleAction() + { + using (var serviceScope = serviceProvider.GetRequiredService().CreateScope()) + { + var context = serviceScope.ServiceProvider.GetService(); + var system = context.Systems.FirstOrDefault(s => s.Id == Guid.Parse((string) action.properties.SelectToken("system")) && s.Secret == (string) action.properties.SelectToken("secret")); + if (system != null) + { + WebSocketManager websocketManager = serviceProvider.GetService(); + websocketManager.SetSystemLink(webSocketConnection.Id, Guid.Parse((string) action.properties.SelectToken("system"))); + base.HandleAction(); + } + } + } + + } +} diff --git a/syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Handlers/UserAuthenticationHandler.cs b/syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Handlers/UserAuthenticationHandler.cs new file mode 100644 index 0000000..8a7260c --- /dev/null +++ b/syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Handlers/UserAuthenticationHandler.cs @@ -0,0 +1,52 @@ +using System; +using System.Linq; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.AspNetCore.Identity; +using Newtonsoft.Json.Linq; +using Syski.Data; + +namespace Syski.WebSocket.Services.WebSockets.Actions.Handlers +{ + public class UserAuthenticationHandler : AuthenticationHandler + { + + public UserAuthenticationHandler(Action action, WebSocketConnection webSocketConnection, IServiceProvider serviceProvider) : base(action, webSocketConnection, serviceProvider) + { + } + + public override async void HandleAction() + { + using (var serviceScope = serviceProvider.GetRequiredService().CreateScope()) + { + var userManager = serviceScope.ServiceProvider.GetService>(); + ApplicationUser user = userManager.Users.SingleOrDefault(r => r.Email.Equals((string) action.properties.SelectToken("email"))); + if (user != null) + { + bool validPassword = await userManager.CheckPasswordAsync(user, (string) action.properties.SelectToken("password")); + if (validPassword) + { + var context = serviceScope.ServiceProvider.GetService(); + var system = new Data.System + { + LastUpdated = DateTime.Now, + Secret = Guid.NewGuid().ToString().Replace("-", "") + }; + context.Add(system); + context.SaveChanges(); + + var applicationUserSystems = new ApplicationUserSystems() + { + UserId = user.Id, + SystemId = system.Id + }; + context.Add(applicationUserSystems); + context.SaveChanges(); + + await webSocketConnection.SendAction("authentication", new JObject { { "system", system.Id }, { "secret", system.Secret } }); + } + } + } + } + + } +} diff --git a/syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Handlers/VariableCPUHandler.cs b/syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Handlers/VariableCPUHandler.cs new file mode 100644 index 0000000..7df1dcb --- /dev/null +++ b/syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Handlers/VariableCPUHandler.cs @@ -0,0 +1,40 @@ +using Microsoft.Extensions.DependencyInjection; +using Syski.Data; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Syski.WebSocket.Services.WebSockets.Actions.Handlers +{ + public class VariableCPUHandler : ActionHandler + { + + public VariableCPUHandler(Action action, WebSocketConnection webSocketConnection, IServiceProvider serviceProvider) : base(action, webSocketConnection, serviceProvider) + { + } + + public override void HandleAction() + { + using (var serviceScope = serviceProvider.GetRequiredService().CreateScope()) + { + var context = serviceScope.ServiceProvider.GetService(); + var systemUUID = serviceProvider.GetService().GetSystemId(webSocketConnection.Id); + + double loadFromJSON = (double) action.properties.SelectToken("load"); + int processesFromJSON = (int) action.properties.SelectToken("processes"); + + var systemCPUData = new SystemCPUData() + { + SystemId = systemUUID, + CollectionDateTime = DateTime.Now, + Load = loadFromJSON, + Processes = processesFromJSON + }; + context.Add(systemCPUData); + context.SaveChanges(); + } + } + + } +} diff --git a/syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Handlers/VariablePingHandler.cs b/syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Handlers/VariablePingHandler.cs new file mode 100644 index 0000000..f579c7e --- /dev/null +++ b/syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Handlers/VariablePingHandler.cs @@ -0,0 +1,32 @@ +using Microsoft.Extensions.DependencyInjection; +using Syski.Data; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Syski.WebSocket.Services.WebSockets.Actions.Handlers +{ + public class VariablePingHandler : ActionHandler + { + + public VariablePingHandler(Action action, WebSocketConnection webSocketConnection, IServiceProvider serviceProvider) : base(action, webSocketConnection, serviceProvider) + { + } + + public override void HandleAction() + { + using (var serviceScope = serviceProvider.GetRequiredService().CreateScope()) + { + var context = serviceScope.ServiceProvider.GetService(); + var systemUUID = serviceProvider.GetService().GetSystemId(webSocketConnection.Id); + + var pingData = context.SystemPingData.OrderByDescending(i => i.SendPingTime).FirstOrDefault(spd => spd.SystemId.Equals(systemUUID)); + pingData.CollectionDateTime = DateTime.Now; + context.Update(pingData); + context.SaveChanges(); + } + } + + } +} diff --git a/syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Handlers/VariableRAMHandler.cs b/syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Handlers/VariableRAMHandler.cs new file mode 100644 index 0000000..89da918 --- /dev/null +++ b/syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Handlers/VariableRAMHandler.cs @@ -0,0 +1,38 @@ +using Microsoft.Extensions.DependencyInjection; +using Syski.Data; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Syski.WebSocket.Services.WebSockets.Actions.Handlers +{ + public class VariableRAMHandler : ActionHandler + { + + public VariableRAMHandler(Action action, WebSocketConnection webSocketConnection, IServiceProvider serviceProvider) : base(action, webSocketConnection, serviceProvider) + { + } + + public override void HandleAction() + { + using (var serviceScope = serviceProvider.GetRequiredService().CreateScope()) + { + var context = serviceScope.ServiceProvider.GetService(); + var systemUUID = serviceProvider.GetService().GetSystemId(webSocketConnection.Id); + + int freeFromJSON = (int) action.properties.SelectToken("free"); + + var systemRAMData = new SystemRAMData() + { + SystemId = systemUUID, + CollectionDateTime = DateTime.Now, + Free = freeFromJSON, + }; + context.Add(systemRAMData); + context.SaveChanges(); + } + } + + } +} diff --git a/syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Handlers/VariableRunningProcessesHandler.cs b/syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Handlers/VariableRunningProcessesHandler.cs new file mode 100644 index 0000000..07f543c --- /dev/null +++ b/syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Handlers/VariableRunningProcessesHandler.cs @@ -0,0 +1,55 @@ +using Microsoft.Extensions.DependencyInjection; +using Newtonsoft.Json.Linq; +using Syski.Data; +using System; + +namespace Syski.WebSocket.Services.WebSockets.Actions.Handlers +{ + public class VariableRunningProcessesHandler : ActionHandler + { + + public VariableRunningProcessesHandler(Action action, WebSocketConnection webSocketConnection, IServiceProvider serviceProvider) : base(action, webSocketConnection, serviceProvider) + { + } + + public override void HandleAction() + { + using (var serviceScope = serviceProvider.GetRequiredService().CreateScope()) + { + var context = serviceScope.ServiceProvider.GetService(); + var systemUUID = serviceProvider.GetService().GetSystemId(webSocketConnection.Id); + DateTime lastUpdated = DateTime.Now; + JArray processesArray = (JArray) action.properties.SelectToken("processes"); + + foreach (JToken process in processesArray) + { + int idFromJSON = (int) process.SelectToken("id"); + string nameFromJSON = (string) process.SelectToken("name"); + long memsizetimeFromJSON = (long) process.SelectToken("memsize"); + long kerneltimeFromJSON = (long) process.SelectToken("kerneltime"); + string pathFromJSON = (string) process.SelectToken("path"); + int threadsFromJSON = (int) process.SelectToken("threads"); + long uptimeFromJSON = (long) process.SelectToken("uptime"); + int parentidFromJSON = (int) process.SelectToken("parentid"); + + var systemProcess = new SystemRunningProcesses() + { + SystemId = systemUUID, + Id = idFromJSON, + Name = nameFromJSON, + MemSize = memsizetimeFromJSON, + KernelTime = kerneltimeFromJSON, + Path = pathFromJSON, + Threads = threadsFromJSON, + UpTime = uptimeFromJSON, + ParentId = parentidFromJSON, + CollectionDateTime = lastUpdated + }; + context.Add(systemProcess); + } + context.SaveChanges(); + } + } + + } +} diff --git a/syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Handlers/VariableStorageHandler.cs b/syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Handlers/VariableStorageHandler.cs new file mode 100644 index 0000000..532f2da --- /dev/null +++ b/syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Handlers/VariableStorageHandler.cs @@ -0,0 +1,49 @@ +using Microsoft.Extensions.DependencyInjection; +using Syski.Data; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Syski.WebSocket.Services.WebSockets.Actions.Handlers +{ + public class VariableStorageHandler : ActionHandler + { + + public VariableStorageHandler(Action action, WebSocketConnection webSocketConnection, IServiceProvider serviceProvider) : base(action, webSocketConnection, serviceProvider) + { + } + + public override void HandleAction() + { + using (var serviceScope = serviceProvider.GetRequiredService().CreateScope()) + { + var context = serviceScope.ServiceProvider.GetService(); + var systemUUID = serviceProvider.GetService().GetSystemId(webSocketConnection.Id); + + float timeFromJSON = (float) action.properties.SelectToken("time"); + float transfersFromJSON = (float) action.properties.SelectToken("transfers"); + float readsFromJSON = (float) action.properties.SelectToken("reads"); + float writesFromJSON = (float) action.properties.SelectToken("writes"); + float byteReadsFromJSON = (float) action.properties.SelectToken("bytereads"); + float byteWritesFromJSON = (float) action.properties.SelectToken("bytewrites"); + DateTime dateTime = DateTime.Now; + + var systemStorageData = new SystemStorageData() + { + SystemId = systemUUID, + CollectionDateTime = dateTime, + Time = timeFromJSON, + Transfers = transfersFromJSON, + Reads = readsFromJSON, + Writes = writesFromJSON, + ByteReads = byteReadsFromJSON, + ByteWrites = byteWritesFromJSON + }; + context.Add(systemStorageData); + context.SaveChanges(); + } + } + + } +} diff --git a/syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Tasks/ActionTask.cs b/syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Tasks/ActionTask.cs new file mode 100644 index 0000000..78470b5 --- /dev/null +++ b/syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Tasks/ActionTask.cs @@ -0,0 +1,22 @@ +using Syski.Data; +using System; + +namespace Syski.WebSocket.Services.WebSockets.Actions.Tasks +{ + public abstract class ActionTask + { + + protected readonly string action; + protected readonly IServiceProvider serviceProvider; + protected readonly SyskiDBContext context; + + public ActionTask(string action, IServiceProvider serviceProvider) + { + this.action = action; + this.serviceProvider = serviceProvider; + } + + public abstract void ExecuteActionTask(WebSocketConnection webSocketConnection); + + } +} diff --git a/syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Tasks/CommandTask.cs b/syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Tasks/CommandTask.cs new file mode 100644 index 0000000..7f8d29e --- /dev/null +++ b/syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Tasks/CommandTask.cs @@ -0,0 +1,42 @@ +using Microsoft.Extensions.DependencyInjection; +using Newtonsoft.Json.Linq; +using Syski.Data; +using System; +using System.Linq; + +namespace Syski.WebSocket.Services.WebSockets.Actions.Tasks +{ + public class CommandTask : ActionTask + { + + public CommandTask(IServiceProvider serviceProvider) : base("command", serviceProvider) + { + } + + public override void ExecuteActionTask(WebSocketConnection webSocketConnection) + { + using (var serviceScope = serviceProvider.GetRequiredService().CreateScope()) + { + var context = serviceScope.ServiceProvider.GetService(); + var systemUUID = serviceProvider.GetService().GetSystemId(webSocketConnection.Id); + var command = context.SystemCommands.FirstOrDefault(sc => sc.SystemId.Equals(systemUUID) && sc.ExecutedTime == null); + if (command != null) + { + command.ExecutedTime = DateTime.Now; + context.Update(command); + if (command.Properties != null) + { + JObject properties = JObject.Parse(command.Properties); + webSocketConnection.SendAction(command.Action, properties); + } + else + { + webSocketConnection.SendAction(command.Action); + } + context.SaveChanges(); + } + } + } + + } +} diff --git a/syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Tasks/DefaultTask.cs b/syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Tasks/DefaultTask.cs new file mode 100644 index 0000000..821b505 --- /dev/null +++ b/syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Tasks/DefaultTask.cs @@ -0,0 +1,23 @@ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Syski.Data; + +namespace Syski.WebSocket.Services.WebSockets.Actions.Tasks +{ + public class DefaultTask : ActionTask + { + + public DefaultTask(string action, IServiceProvider serviceProvider) : base(action, serviceProvider) + { + } + + public override void ExecuteActionTask(WebSocketConnection webSocketConnection) + { + webSocketConnection.SendAction(action); + } + + } +} diff --git a/syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Tasks/VariablePingTask.cs b/syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Tasks/VariablePingTask.cs new file mode 100644 index 0000000..9f004f1 --- /dev/null +++ b/syski_api/uk.co.syski.websocket/Services/WebSockets/Actions/Tasks/VariablePingTask.cs @@ -0,0 +1,31 @@ +using System; +using Microsoft.Extensions.DependencyInjection; +using Syski.Data; + +namespace Syski.WebSocket.Services.WebSockets.Actions.Tasks +{ + public class VariablePingTask : ActionTask + { + + public VariablePingTask(IServiceProvider serviceProvider) : base("variableping", serviceProvider) + { + } + + public override void ExecuteActionTask(WebSocketConnection webSocketConnection) + { + using (var serviceScope = serviceProvider.GetRequiredService().CreateScope()) + { + var context = serviceScope.ServiceProvider.GetService(); + var systemUUID = serviceProvider.GetService().GetSystemId(webSocketConnection.Id); + context.Add(new SystemPingData + { + SystemId = systemUUID, + SendPingTime = DateTime.Now + }); + context.SaveChanges(); + webSocketConnection.SendAction(action); + } + } + + } +} diff --git a/syski_api/uk.co.syski.websocket/Services/WebSockets/IWebSocketHandler.cs b/syski_api/uk.co.syski.websocket/Services/WebSockets/IWebSocketHandler.cs new file mode 100644 index 0000000..383f81e --- /dev/null +++ b/syski_api/uk.co.syski.websocket/Services/WebSockets/IWebSocketHandler.cs @@ -0,0 +1,15 @@ +using System.Threading.Tasks; + +namespace Syski.WebSocket.Services.WebSockets +{ + public interface IWebSocketHandler + { + + Task OnConnected(WebSocketConnection webSocketConnection); + + Task OnDisconnected(WebSocketConnection webSocketConnection); + + Task OnReceiveMessage(WebSocketConnection webSocketConnection); + + } +} diff --git a/syski_api/uk.co.syski.websocket/Services/WebSockets/WebSocketConnection.cs b/syski_api/uk.co.syski.websocket/Services/WebSockets/WebSocketConnection.cs new file mode 100644 index 0000000..a174d7d --- /dev/null +++ b/syski_api/uk.co.syski.websocket/Services/WebSockets/WebSocketConnection.cs @@ -0,0 +1,62 @@ +using System; +using System.Net.WebSockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Syski.WebSocket.Services.WebSockets.Actions; + +namespace Syski.WebSocket.Services.WebSockets +{ + public class WebSocketConnection + { + + public Guid Id { get; } = Guid.NewGuid(); + + public System.Net.WebSockets.WebSocket WebSocket { get; } + + public WebSocketCloseStatus? CloseStatus { get; set; } = null; + + public bool Authentication { get; set; } = false; + + public string CloseStatusDescription { get; set; } = null; + + public CancellationTokenSource CancellationTokenSource; + + public WebSocketConnection(System.Net.WebSockets.WebSocket webSocket) + { + WebSocket = webSocket; + CancellationTokenSource = new CancellationTokenSource(60000); + } + + public void ResetCancelationToken() + { + CancellationTokenSource.CancelAfter(20000); + } + + public CancellationToken GetCancellationToken() + { + return CancellationTokenSource.Token; + } + + public async Task SendAction(String actionName) + { + await SendMessage(JsonConvert.SerializeObject(ActionFactory.CreateAction(actionName))); + } + + public async Task SendAction(String actionName, JObject actionProperties) + { + await SendMessage(JsonConvert.SerializeObject(ActionFactory.CreateAction(actionName, actionProperties))); + } + + private async Task SendMessage(String message) + { + if (WebSocket.State == WebSocketState.Open) + { + await WebSocket.SendAsync(buffer: new ArraySegment(array: Encoding.ASCII.GetBytes(message), offset: 0, count: message.Length), messageType: WebSocketMessageType.Text, endOfMessage: true, cancellationToken: CancellationToken.None); + } + } + + } +} \ No newline at end of file diff --git a/syski_api/uk.co.syski.websocket/Services/WebSockets/WebSocketHandler.cs b/syski_api/uk.co.syski.websocket/Services/WebSockets/WebSocketHandler.cs new file mode 100644 index 0000000..c0b7cf0 --- /dev/null +++ b/syski_api/uk.co.syski.websocket/Services/WebSockets/WebSocketHandler.cs @@ -0,0 +1,127 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Syski.WebSocket.Services.WebSockets.Actions; +using System; +using System.IO; +using System.Net.WebSockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Syski.WebSocket.Services.WebSockets +{ + public class WebSocketHandler : IWebSocketHandler + { + + private readonly IServiceProvider serviceProvider; + private readonly WebSocketManager webSocketManager; + + public WebSocketHandler(IServiceProvider serviceProvider, WebSocketManager webSocketManager) + { + this.serviceProvider = serviceProvider; + this.webSocketManager = webSocketManager; + } + + public async Task OnConnected(WebSocketConnection webSocketConnection) + { + await webSocketConnection.SendAction("authentication"); + } + + public async Task OnDisconnected(WebSocketConnection webSocketConnection) + { + webSocketManager.RemoveSocket(webSocketConnection.Id); + if (webSocketConnection.CloseStatus.HasValue) + { + await webSocketConnection.WebSocket.CloseAsync(webSocketConnection.CloseStatus.Value, webSocketConnection.CloseStatusDescription, CancellationToken.None); + } + } + + public async Task OnReceiveMessage(WebSocketConnection webSocketConnection) + { + try + { + byte[] receivePayloadBuffer = new byte[4 * 1024]; + WebSocketReceiveResult webSocketReceiveResult = await webSocketConnection.WebSocket.ReceiveAsync(new ArraySegment(receivePayloadBuffer), webSocketConnection.GetCancellationToken()); + webSocketConnection.ResetCancelationToken(); + while (webSocketReceiveResult.MessageType != WebSocketMessageType.Close) + { + byte[] result = await ReceiveMessagePayloadAsync(webSocketConnection.WebSocket, webSocketReceiveResult, receivePayloadBuffer); + try + { + Actions.Action action = JsonConvert.DeserializeObject(Encoding.UTF8.GetString(result, 0, result.Length)); + if (webSocketConnection.Authentication) + { + ActionFactory.CreateActionHandler(action, webSocketConnection, serviceProvider).HandleAction(); + } + else + { + ActionFactory.CreateAuthActionHandler(action, webSocketConnection, serviceProvider).HandleAction(); + } + } + /*catch (JsonReaderException e) + { + var properties = new JObject { { "message", "Invalid message format sent" } }; + await webSocketConnection.sendAction("error", properties); + } + */ + catch(Exception e) + { + if (e is NotImplementedException) + { + var properties = new JObject { { "message", "This API version does not support this action yet" } }; + await webSocketConnection.SendAction("error", properties); + } + else if (e is JsonReaderException) + { + var properties = new JObject { { "message", "Invalid message format sent" } }; + await webSocketConnection.SendAction("error", properties); + } + } + webSocketReceiveResult = await webSocketConnection.WebSocket.ReceiveAsync(new ArraySegment(receivePayloadBuffer), webSocketConnection.GetCancellationToken()); + webSocketConnection.ResetCancelationToken(); + } + webSocketConnection.CloseStatus = webSocketReceiveResult.CloseStatus.Value; + webSocketConnection.CloseStatusDescription = webSocketReceiveResult.CloseStatusDescription; + } + /*catch (WebSocketException wsex) when (wsex.WebSocketErrorCode == WebSocketError.ConnectionClosedPrematurely) + { + + } + catch (OperationCanceledException oce) + { + + } + */ + catch + { + + } + } + + private static async Task ReceiveMessagePayloadAsync(System.Net.WebSockets.WebSocket webSocket, WebSocketReceiveResult webSocketReceiveResult, byte[] receivePayloadBuffer) + { + byte[] messagePayload = null; + + if (webSocketReceiveResult.EndOfMessage) + { + messagePayload = new byte[webSocketReceiveResult.Count]; + Array.Copy(receivePayloadBuffer, messagePayload, webSocketReceiveResult.Count); + } + else + { + using (MemoryStream messagePayloadStream = new MemoryStream()) + { + messagePayloadStream.Write(receivePayloadBuffer, 0, webSocketReceiveResult.Count); + while (!webSocketReceiveResult.EndOfMessage) + { + webSocketReceiveResult = await webSocket.ReceiveAsync(new ArraySegment(receivePayloadBuffer), CancellationToken.None); + messagePayloadStream.Write(receivePayloadBuffer, 0, webSocketReceiveResult.Count); + } + messagePayload = messagePayloadStream.ToArray(); + } + } + return messagePayload; + } + + } +} diff --git a/syski_api/uk.co.syski.websocket/Services/WebSockets/WebSocketManager.cs b/syski_api/uk.co.syski.websocket/Services/WebSockets/WebSocketManager.cs new file mode 100644 index 0000000..230160e --- /dev/null +++ b/syski_api/uk.co.syski.websocket/Services/WebSockets/WebSocketManager.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Concurrent; +using System.Linq; + +namespace Syski.WebSocket.Services.WebSockets +{ + public class WebSocketManager + { + + private ConcurrentDictionary webSockets = new ConcurrentDictionary(); + private ConcurrentDictionary webSocketSystem = new ConcurrentDictionary(); + + public WebSocketManager() + { + } + + public ConcurrentDictionary GetWebSockets() + { + return webSockets; + } + + public WebSocketConnection GetSocketById(Guid id) + { + return webSockets.FirstOrDefault(p => p.Key == id).Value; + } + + public Guid GetId(WebSocketConnection webSocketConnection) + { + return webSockets.FirstOrDefault(p => p.Value == webSocketConnection).Key; + } + + public Guid GetSystemId(Guid webSocketConnectionId) + { + webSocketSystem.TryGetValue(webSocketConnectionId, out Guid result); + return result; + } + + public void AddSocket(Guid webSocketConnectionId, WebSocketConnection webSocketConnection) + { + webSockets.TryAdd(webSocketConnectionId, webSocketConnection); + } + + public void SetSystemLink(Guid webSocketConnectionId, Guid SystemId) + { + webSocketSystem.TryAdd(webSocketConnectionId, SystemId); + } + + public void RemoveSocket(Guid webSocketConnectionId) + { + webSockets.TryRemove(webSocketConnectionId, out WebSocketConnection webSocketConnection); + webSocketSystem.TryRemove(webSocketConnectionId, out Guid systemId); + } + + } +} \ No newline at end of file diff --git a/syski_api/uk.co.syski.websocket/Services/WebSockets/WebSocketMiddleware.cs b/syski_api/uk.co.syski.websocket/Services/WebSockets/WebSocketMiddleware.cs new file mode 100644 index 0000000..2b5a201 --- /dev/null +++ b/syski_api/uk.co.syski.websocket/Services/WebSockets/WebSocketMiddleware.cs @@ -0,0 +1,38 @@ +using Microsoft.AspNetCore.Http; +using System.Threading.Tasks; + +namespace Syski.WebSocket.Services.WebSockets +{ + public class WebSocketMiddleware + { + + private readonly RequestDelegate next; + private readonly IWebSocketHandler webSocketHandler; + + public WebSocketMiddleware(RequestDelegate next, IWebSocketHandler webSocketHandler) + { + this.next = next; + this.webSocketHandler = webSocketHandler; + } + + public async Task Invoke(HttpContext context) + { + if (context.WebSockets.IsWebSocketRequest) + { + var webSocket = await context.WebSockets.AcceptWebSocketAsync(); + WebSocketConnection webSocketConnection = new WebSocketConnection(webSocket); + + await webSocketHandler.OnConnected(webSocketConnection); + + await webSocketHandler.OnReceiveMessage(webSocketConnection); + + await webSocketHandler.OnDisconnected(webSocketConnection); + } + else + { + await next.Invoke(context); + } + } + + } +} diff --git a/syski_api/uk.co.syski.websocket/Services/WebSockets/WebSocketMiddlewareExtensions.cs b/syski_api/uk.co.syski.websocket/Services/WebSockets/WebSocketMiddlewareExtensions.cs new file mode 100644 index 0000000..6efdc51 --- /dev/null +++ b/syski_api/uk.co.syski.websocket/Services/WebSockets/WebSocketMiddlewareExtensions.cs @@ -0,0 +1,15 @@ +using Microsoft.AspNetCore.Builder; + +namespace Syski.WebSocket.Services.WebSockets +{ + public static class WebSocketMiddlewareExtensions + { + + public static IApplicationBuilder UseWebSocketMiddleware(this IApplicationBuilder app) + { + app.UseMiddleware(); + return app; + } + + } +} diff --git a/syski_api/uk.co.syski.websocket/Services/WebSockets/WebSocketTaskScheduler.cs b/syski_api/uk.co.syski.websocket/Services/WebSockets/WebSocketTaskScheduler.cs new file mode 100644 index 0000000..f062ca3 --- /dev/null +++ b/syski_api/uk.co.syski.websocket/Services/WebSockets/WebSocketTaskScheduler.cs @@ -0,0 +1,70 @@ +using Microsoft.Extensions.Hosting; +using Syski.Data; +using Syski.WebSocket.Services.WebSockets.Actions.Tasks; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Syski.WebSocket.Services.WebSockets +{ + public class WebSocketTaskScheduler : IHostedService + { + + private readonly WebSocketManager webSocketManager; + private readonly IServiceProvider serviceProvider; + private List timers = new List(); + + public WebSocketTaskScheduler(WebSocketManager webSocketManager, IServiceProvider serviceProvider) + { + this.webSocketManager = webSocketManager; + this.serviceProvider = serviceProvider; + } + + public Task StartAsync(CancellationToken cancellationToken) + { + timers.Add(new Timer(SendAction, new CommandTask(serviceProvider), TimeSpan.Zero, TimeSpan.FromSeconds(1))); + timers.Add(new Timer(SendAction, new VariablePingTask(serviceProvider), TimeSpan.Zero, TimeSpan.FromSeconds(10))); + timers.Add(new Timer(SendAction, new DefaultTask("variablecpu", serviceProvider), TimeSpan.Zero, TimeSpan.FromSeconds(3))); + timers.Add(new Timer(SendAction, new DefaultTask("variableram", serviceProvider), TimeSpan.Zero, TimeSpan.FromSeconds(3))); + timers.Add(new Timer(SendAction, new DefaultTask("variablestorage", serviceProvider), TimeSpan.Zero, TimeSpan.FromSeconds(3))); + //timers.Add(new Timer(SendAction, new DefaultTask("variablenetwork", serviceProvider), TimeSpan.Zero, TimeSpan.FromSeconds(3))); + timers.Add(new Timer(SendAction, new DefaultTask("processes", serviceProvider), TimeSpan.Zero, TimeSpan.FromSeconds(30))); + return Task.CompletedTask; + } + + public Task StopAsync(CancellationToken cancellationToken) + { + foreach (Timer timer in timers) + { + timer?.Change(Timeout.Infinite, 0); + } + return Task.CompletedTask; + } + + public void Dispose() + { + foreach (Timer timer in timers) + { + timer?.Dispose(); + } + } + + private void SendAction(object state) + { + ActionTask actionTask = (ActionTask) state; + foreach (var webSocketConnection in webSocketManager.GetWebSockets()) + { + try + { + actionTask.ExecuteActionTask(webSocketConnection.Value); + } + catch + { + + } + } + } + + } +} diff --git a/syski_api/uk.co.syski.websocket/Startup.cs b/syski_api/uk.co.syski.websocket/Startup.cs new file mode 100644 index 0000000..c793650 --- /dev/null +++ b/syski_api/uk.co.syski.websocket/Startup.cs @@ -0,0 +1,112 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.HttpOverrides; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Syski.Data; +using Syski.WebSocket.Services.WebSockets; +using System; + +namespace Syski.WebSocket +{ + public class Startup + { + + public IConfiguration Configuration { get; } + + public Startup(IConfiguration configuration) + { + Configuration = configuration; + } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + // Make all the urls lowercase as this is good web practice + services.AddRouting(options => options.LowercaseUrls = true); + + // Load the connection string from the settings file and use it for storing data + services.AddDbContext(options => + options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")) + ); + + // Add Identity to the application + services.AddDefaultIdentity() + .AddEntityFrameworkStores(); + + services.AddHostedService(); + + services.AddTransient(); + services.AddSingleton(); + + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IHostingEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + else + { + app.UseHsts(); + } + + // Check if behind a reverse proxy if so use Forwarded Headers for the connection information + try + { + if (Convert.ToBoolean(Configuration["ReverseProxy"])) + { + app.UseForwardedHeaders(new ForwardedHeadersOptions + { + ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto + }); + } + } + /*catch (FormatException fe) + { + // Error parsing config, do nothing assume not behind a reverse proxy + } + */ + catch + { + + } + + // Load WebSocket options from the config file + var wsOptions = new WebSocketOptions(); + try + { + wsOptions.KeepAliveInterval = TimeSpan.FromSeconds(Convert.ToInt32(Configuration["WebSocket:KeepAliveInterval"])); + } + catch + { + // Default option of keep alive set to 2 minutes + wsOptions.KeepAliveInterval = TimeSpan.FromSeconds(120); + } + try + { + wsOptions.ReceiveBufferSize = Convert.ToInt32(Configuration["WebSocket:ReceiveBufferSize"]); + } + catch + { + // Default option of buffer set to 4096 bytes + wsOptions.ReceiveBufferSize = 4096; + } + + app.UseHttpsRedirection(); + + app.UseWebSockets(wsOptions); + + app.UseWebSocketMiddleware(); + + app.Run(async (context) => + { + await context.Response.WriteAsync("Error: Not a websocket"); + }); + } + } +} diff --git a/syski_api/uk.co.syski.websocket/Syski.WebSocket.csproj b/syski_api/uk.co.syski.websocket/Syski.WebSocket.csproj new file mode 100644 index 0000000..0057816 --- /dev/null +++ b/syski_api/uk.co.syski.websocket/Syski.WebSocket.csproj @@ -0,0 +1,16 @@ + + + + netcoreapp2.1 + + + + + + + + + + + + diff --git a/syski_api/uk.co.syski.websocket/appsettings.Development.json b/syski_api/uk.co.syski.websocket/appsettings.Development.json new file mode 100644 index 0000000..e203e94 --- /dev/null +++ b/syski_api/uk.co.syski.websocket/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + } +} diff --git a/syski_api/uk.co.syski.websocket/appsettings.json b/syski_api/uk.co.syski.websocket/appsettings.json new file mode 100644 index 0000000..acf7541 --- /dev/null +++ b/syski_api/uk.co.syski.websocket/appsettings.json @@ -0,0 +1,16 @@ +{ + "ReverseProxy": true, + "WebSocket": { + "KeepAliveInterval": 120, + "ReceiveBufferSize": 4096 + }, + "ConnectionStrings": { + "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=SyskiAPI;Trusted_Connection=True;MultipleActiveResultSets=true" + }, + "Logging": { + "LogLevel": { + "Default": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/syski_client_android/.gitignore b/syski_client_android/.gitignore new file mode 100644 index 0000000..39b6783 --- /dev/null +++ b/syski_client_android/.gitignore @@ -0,0 +1,65 @@ +# Built application files +*.apk +*.ap_ + +# Files for the ART/Dalvik VM +*.dex + +# Java class files +*.class + +# Generated files +bin/ +gen/ +out/ + +# Gradle files +.gradle/ +build/ + +# Local configuration file (sdk path, etc) +local.properties + +# Proguard folder generated by Eclipse +proguard/ + +# Log Files +*.log + +# Android Studio Navigation editor temp files +.navigation/ + +# Android Studio captures folder +captures/ + +# IntelliJ +*.iml +.idea/workspace.xml +.idea/tasks.xml +.idea/gradle.xml +.idea/assetWizardSettings.xml +.idea/dictionaries +.idea/libraries +.idea/caches + +# Keystore files +# Uncomment the following line if you do not want to check your keystore files in. +#*.jks + +# External native build folder generated in Android Studio 2.2 and later +.externalNativeBuild + +# Google Services (e.g. APIs or Firebase) +google-services.json + +# Freeline +freeline.py +freeline/ +freeline_project_description.json + +# fastlane +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots +fastlane/test_output +fastlane/readme.md diff --git a/syski_client_android/.idea/misc.xml b/syski_client_android/.idea/misc.xml new file mode 100644 index 0000000..3963879 --- /dev/null +++ b/syski_client_android/.idea/misc.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/syski_client_android/.idea/modules.xml b/syski_client_android/.idea/modules.xml new file mode 100644 index 0000000..4eb8808 --- /dev/null +++ b/syski_client_android/.idea/modules.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/syski_client_android/.idea/runConfigurations.xml b/syski_client_android/.idea/runConfigurations.xml new file mode 100644 index 0000000..7f68460 --- /dev/null +++ b/syski_client_android/.idea/runConfigurations.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/syski_client_android/.idea/vcs.xml b/syski_client_android/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/syski_client_android/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/syski_client_android/.travis.yml b/syski_client_android/.travis.yml new file mode 100644 index 0000000..e69de29 diff --git a/syski_client_android/LICENSE b/syski_client_android/LICENSE new file mode 100644 index 0000000..631ecf5 --- /dev/null +++ b/syski_client_android/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its userEntities. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both userEntities' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny userEntities access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting userEntities' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of userEntities. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a userEntity through +a computer network, with no transfer of a copy, is not conveying. + + An interactive userEntity interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the userEntity that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of userEntity commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window systemEntity, and so on) of the specific operating systemEntity +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that userEntities +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting userEntities' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +userEntities, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive userEntity interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's userEntities +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular userEntity, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular userEntity or of the way in which the particular userEntity +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + 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 3 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, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/syski_client_android/README.md b/syski_client_android/README.md new file mode 100644 index 0000000..e4036c7 --- /dev/null +++ b/syski_client_android/README.md @@ -0,0 +1 @@ +# syski_client_android \ No newline at end of file diff --git a/syski_client_android/app/.gitignore b/syski_client_android/app/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/syski_client_android/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/syski_client_android/app/build.gradle b/syski_client_android/app/build.gradle new file mode 100644 index 0000000..0932cc5 --- /dev/null +++ b/syski_client_android/app/build.gradle @@ -0,0 +1,48 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 26 + defaultConfig { + applicationId "uk.co.syski.client.android" + minSdkVersion 21 + targetSdkVersion 26 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + //noinspection GradleCompatible + implementation 'com.android.support:appcompat-v7:26.1.0' + implementation 'com.google.firebase:firebase-core:16.0.1' + implementation 'com.google.firebase:firebase-messaging:17.5.0' + implementation 'com.android.support.constraint:constraint-layout:1.1.3' + implementation 'com.android.support:design:26.1.0' + implementation 'com.android.support:support-v4:26.1.0' + testImplementation 'junit:junit:4.12' + androidTestImplementation 'com.android.support.test:runner:1.0.2' + androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' + compile 'com.android.support:support-annotations:27.1.1' + compile 'com.android.volley:volley:1.1.1' + implementation 'com.journeyapps:zxing-android-embedded:3.6.0' + implementation 'com.android.support:gridlayout-v7:26.1.0' + def room_version = "1.1.1" + implementation "android.arch.persistence.room:runtime:$room_version" + annotationProcessor "android.arch.persistence.room:compiler:$room_version" + def lifecycle_version = "1.1.1" + implementation "android.arch.lifecycle:extensions:$lifecycle_version" + annotationProcessor "android.arch.lifecycle:compiler:$lifecycle_version" + debugImplementation 'com.amitshekhar.android:debug-db:1.0.6' + implementation 'com.jjoe64:graphview:4.2.1' + testImplementation 'org.mockito:mockito-core:1.10.19' +} +apply plugin: 'com.google.gms.google-services' \ No newline at end of file diff --git a/syski_client_android/app/proguard-rules.pro b/syski_client_android/app/proguard-rules.pro new file mode 100644 index 0000000..f1b4245 --- /dev/null +++ b/syski_client_android/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/syski_client_android/app/src/androidTest/java/uk/co/syski/client/android/ExampleInstrumentedTest.java b/syski_client_android/app/src/androidTest/java/uk/co/syski/client/android/ExampleInstrumentedTest.java new file mode 100644 index 0000000..13664c6 --- /dev/null +++ b/syski_client_android/app/src/androidTest/java/uk/co/syski/client/android/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package uk.co.syski.client.android; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() throws Exception { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getTargetContext(); + + assertEquals("uk.co.syski.client.android", appContext.getPackageName()); + } +} diff --git a/syski_client_android/app/src/androidTest/java/uk/co/syski/client/android/view/CPUActivityTest.java b/syski_client_android/app/src/androidTest/java/uk/co/syski/client/android/view/CPUActivityTest.java new file mode 100644 index 0000000..0bf42b9 --- /dev/null +++ b/syski_client_android/app/src/androidTest/java/uk/co/syski/client/android/view/CPUActivityTest.java @@ -0,0 +1,20 @@ +package uk.co.syski.client.android.view; + +import org.junit.Test; + +import static org.junit.Assert.*; + +public class CPUActivityTest { + @Test + public void onCreate() throws Exception { + } + + @Test + public void onCreateOptionsMenu() throws Exception { + } + + @Test + public void onOptionsItemSelected() throws Exception { + } + +} \ No newline at end of file diff --git a/syski_client_android/app/src/androidTest/java/uk/co/syski/client/android/view/GPUActivityTest.java b/syski_client_android/app/src/androidTest/java/uk/co/syski/client/android/view/GPUActivityTest.java new file mode 100644 index 0000000..1180a54 --- /dev/null +++ b/syski_client_android/app/src/androidTest/java/uk/co/syski/client/android/view/GPUActivityTest.java @@ -0,0 +1,20 @@ +package uk.co.syski.client.android.view; + +import org.junit.Test; + +import static org.junit.Assert.*; + +public class GPUActivityTest { + @Test + public void onCreate() throws Exception { + } + + @Test + public void onCreateOptionsMenu() throws Exception { + } + + @Test + public void onOptionsItemSelected() throws Exception { + } + +} \ No newline at end of file diff --git a/syski_client_android/app/src/androidTest/java/uk/co/syski/client/android/view/MOBOActivityTest.java b/syski_client_android/app/src/androidTest/java/uk/co/syski/client/android/view/MOBOActivityTest.java new file mode 100644 index 0000000..42e6aa5 --- /dev/null +++ b/syski_client_android/app/src/androidTest/java/uk/co/syski/client/android/view/MOBOActivityTest.java @@ -0,0 +1,20 @@ +package uk.co.syski.client.android.view; + +import org.junit.Test; + +import static org.junit.Assert.*; + +public class MOBOActivityTest { + @Test + public void onCreate() throws Exception { + } + + @Test + public void onCreateOptionsMenu() throws Exception { + } + + @Test + public void onOptionsItemSelected() throws Exception { + } + +} \ No newline at end of file diff --git a/syski_client_android/app/src/androidTest/java/uk/co/syski/client/android/view/RAMActivityTest.java b/syski_client_android/app/src/androidTest/java/uk/co/syski/client/android/view/RAMActivityTest.java new file mode 100644 index 0000000..0d04161 --- /dev/null +++ b/syski_client_android/app/src/androidTest/java/uk/co/syski/client/android/view/RAMActivityTest.java @@ -0,0 +1,20 @@ +package uk.co.syski.client.android.view; + +import org.junit.Test; + +import static org.junit.Assert.*; + +public class RAMActivityTest { + @Test + public void onCreate() throws Exception { + } + + @Test + public void onCreateOptionsMenu() throws Exception { + } + + @Test + public void onOptionsItemSelected() throws Exception { + } + +} \ No newline at end of file diff --git a/syski_client_android/app/src/androidTest/java/uk/co/syski/client/android/view/SettingsActivityTest.java b/syski_client_android/app/src/androidTest/java/uk/co/syski/client/android/view/SettingsActivityTest.java new file mode 100644 index 0000000..9c8ecbb --- /dev/null +++ b/syski_client_android/app/src/androidTest/java/uk/co/syski/client/android/view/SettingsActivityTest.java @@ -0,0 +1,28 @@ +package uk.co.syski.client.android.view; + +import org.junit.Test; + +import static org.junit.Assert.*; + +public class SettingsActivityTest { + @Test + public void onCreate() throws Exception { + } + + @Test + public void onOptionsItemSelected() throws Exception { + } + + @Test + public void onIsMultiPane() throws Exception { + } + + @Test + public void onBuildHeaders() throws Exception { + } + + @Test + public void isValidFragment() throws Exception { + } + +} \ No newline at end of file diff --git a/syski_client_android/app/src/androidTest/java/uk/co/syski/client/android/view/SystemListMenuTest.java b/syski_client_android/app/src/androidTest/java/uk/co/syski/client/android/view/SystemListMenuTest.java new file mode 100644 index 0000000..f6a6b06 --- /dev/null +++ b/syski_client_android/app/src/androidTest/java/uk/co/syski/client/android/view/SystemListMenuTest.java @@ -0,0 +1,32 @@ +package uk.co.syski.client.android.view; + +import org.junit.Test; + +import static org.junit.Assert.*; + +public class SystemListMenuTest { + @Test + public void onCreate() throws Exception { + } + + @Test + public void onBackPressed() throws Exception { + } + + @Test + public void onCreateOptionsMenu() throws Exception { + } + + @Test + public void onOptionsItemSelected() throws Exception { + } + + @Test + public void onNavigationItemSelected() throws Exception { + } + + @Test + public void onActivityResult() throws Exception { + } + +} \ No newline at end of file diff --git a/syski_client_android/app/src/androidTest/java/uk/co/syski/client/android/view/SystemOSActivityTest.java b/syski_client_android/app/src/androidTest/java/uk/co/syski/client/android/view/SystemOSActivityTest.java new file mode 100644 index 0000000..62a6d47 --- /dev/null +++ b/syski_client_android/app/src/androidTest/java/uk/co/syski/client/android/view/SystemOSActivityTest.java @@ -0,0 +1,20 @@ +package uk.co.syski.client.android.view; + +import org.junit.Test; + +import static org.junit.Assert.*; + +public class SystemOSActivityTest { + @Test + public void onCreate() throws Exception { + } + + @Test + public void onCreateOptionsMenu() throws Exception { + } + + @Test + public void onOptionsItemSelected() throws Exception { + } + +} \ No newline at end of file diff --git a/syski_client_android/app/src/androidTest/java/uk/co/syski/client/android/view/SystemOverviewActivityTest.java b/syski_client_android/app/src/androidTest/java/uk/co/syski/client/android/view/SystemOverviewActivityTest.java new file mode 100644 index 0000000..6a4a914 --- /dev/null +++ b/syski_client_android/app/src/androidTest/java/uk/co/syski/client/android/view/SystemOverviewActivityTest.java @@ -0,0 +1,28 @@ +package uk.co.syski.client.android.view; + +import org.junit.Test; + +import static org.junit.Assert.*; + +public class SystemOverviewActivityTest { + @Test + public void onCreate() throws Exception { + } + + @Test + public void shutdownOnClick() throws Exception { + } + + @Test + public void restartOnClick() throws Exception { + } + + @Test + public void onCreateOptionsMenu() throws Exception { + } + + @Test + public void onOptionsItemSelected() throws Exception { + } + +} \ No newline at end of file diff --git a/syski_client_android/app/src/androidTest/java/uk/co/syski/client/android/view/SystemStorageActivityTest.java b/syski_client_android/app/src/androidTest/java/uk/co/syski/client/android/view/SystemStorageActivityTest.java new file mode 100644 index 0000000..e159b61 --- /dev/null +++ b/syski_client_android/app/src/androidTest/java/uk/co/syski/client/android/view/SystemStorageActivityTest.java @@ -0,0 +1,20 @@ +package uk.co.syski.client.android.view; + +import org.junit.Test; + +import static org.junit.Assert.*; + +public class SystemStorageActivityTest { + @Test + public void onCreate() throws Exception { + } + + @Test + public void onCreateOptionsMenu() throws Exception { + } + + @Test + public void onOptionsItemSelected() throws Exception { + } + +} \ No newline at end of file diff --git a/syski_client_android/app/src/androidTest/java/uk/co/syski/client/android/view/adapter/CPUAdapterTest.java b/syski_client_android/app/src/androidTest/java/uk/co/syski/client/android/view/adapter/CPUAdapterTest.java new file mode 100644 index 0000000..d69a8d5 --- /dev/null +++ b/syski_client_android/app/src/androidTest/java/uk/co/syski/client/android/view/adapter/CPUAdapterTest.java @@ -0,0 +1,12 @@ +package uk.co.syski.client.android.view.adapter; + +import org.junit.Test; + +import static org.junit.Assert.*; + +public class CPUAdapterTest { + @Test + public void getView() throws Exception { + } + +} \ No newline at end of file diff --git a/syski_client_android/app/src/androidTest/java/uk/co/syski/client/android/view/adapter/DoubleHeadedValueListAdapterTest.java b/syski_client_android/app/src/androidTest/java/uk/co/syski/client/android/view/adapter/DoubleHeadedValueListAdapterTest.java new file mode 100644 index 0000000..d91eb77 --- /dev/null +++ b/syski_client_android/app/src/androidTest/java/uk/co/syski/client/android/view/adapter/DoubleHeadedValueListAdapterTest.java @@ -0,0 +1,16 @@ +package uk.co.syski.client.android.view.adapter; + +import org.junit.Test; + +import static org.junit.Assert.*; + +public class DoubleHeadedValueListAdapterTest { + @Test + public void getView() throws Exception { + } + + @Test + public void initViews() throws Exception { + } + +} \ No newline at end of file diff --git a/syski_client_android/app/src/androidTest/java/uk/co/syski/client/android/view/adapter/HeadedValueListAdapterTest.java b/syski_client_android/app/src/androidTest/java/uk/co/syski/client/android/view/adapter/HeadedValueListAdapterTest.java new file mode 100644 index 0000000..77ae600 --- /dev/null +++ b/syski_client_android/app/src/androidTest/java/uk/co/syski/client/android/view/adapter/HeadedValueListAdapterTest.java @@ -0,0 +1,16 @@ +package uk.co.syski.client.android.view.adapter; + +import org.junit.Test; + +import static org.junit.Assert.*; + +public class HeadedValueListAdapterTest { + @Test + public void getView() throws Exception { + } + + @Test + public void initViews() throws Exception { + } + +} \ No newline at end of file diff --git a/syski_client_android/app/src/androidTest/java/uk/co/syski/client/android/view/adapter/RAMAdapterTest.java b/syski_client_android/app/src/androidTest/java/uk/co/syski/client/android/view/adapter/RAMAdapterTest.java new file mode 100644 index 0000000..f301748 --- /dev/null +++ b/syski_client_android/app/src/androidTest/java/uk/co/syski/client/android/view/adapter/RAMAdapterTest.java @@ -0,0 +1,16 @@ +package uk.co.syski.client.android.view.adapter; + +import org.junit.Test; + +import static org.junit.Assert.*; + +public class RAMAdapterTest { + @Test + public void getView() throws Exception { + } + + @Test + public void formatBytes() throws Exception { + } + +} \ No newline at end of file diff --git a/syski_client_android/app/src/androidTest/java/uk/co/syski/client/android/view/adapter/StorageAdapterTest.java b/syski_client_android/app/src/androidTest/java/uk/co/syski/client/android/view/adapter/StorageAdapterTest.java new file mode 100644 index 0000000..6438d70 --- /dev/null +++ b/syski_client_android/app/src/androidTest/java/uk/co/syski/client/android/view/adapter/StorageAdapterTest.java @@ -0,0 +1,16 @@ +package uk.co.syski.client.android.view.adapter; + +import org.junit.Test; + +import static org.junit.Assert.*; + +public class StorageAdapterTest { + @Test + public void getView() throws Exception { + } + + @Test + public void formatBytes() throws Exception { + } + +} \ No newline at end of file diff --git a/syski_client_android/app/src/androidTest/java/uk/co/syski/client/android/view/fragment/DoubleHeadedValueFragmentTest.java b/syski_client_android/app/src/androidTest/java/uk/co/syski/client/android/view/fragment/DoubleHeadedValueFragmentTest.java new file mode 100644 index 0000000..1b87503 --- /dev/null +++ b/syski_client_android/app/src/androidTest/java/uk/co/syski/client/android/view/fragment/DoubleHeadedValueFragmentTest.java @@ -0,0 +1,20 @@ +package uk.co.syski.client.android.view.fragment; + +import org.junit.Test; + +import static org.junit.Assert.*; + +public class DoubleHeadedValueFragmentTest { + @Test + public void newInstance() throws Exception { + } + + @Test + public void onCreateView() throws Exception { + } + + @Test + public void onViewCreated() throws Exception { + } + +} \ No newline at end of file diff --git a/syski_client_android/app/src/androidTest/java/uk/co/syski/client/android/view/fragment/HeadedValueFragmentTest.java b/syski_client_android/app/src/androidTest/java/uk/co/syski/client/android/view/fragment/HeadedValueFragmentTest.java new file mode 100644 index 0000000..d37f112 --- /dev/null +++ b/syski_client_android/app/src/androidTest/java/uk/co/syski/client/android/view/fragment/HeadedValueFragmentTest.java @@ -0,0 +1,20 @@ +package uk.co.syski.client.android.view.fragment; + +import org.junit.Test; + +import static org.junit.Assert.*; + +public class HeadedValueFragmentTest { + @Test + public void newInstance() throws Exception { + } + + @Test + public void onCreateView() throws Exception { + } + + @Test + public void onViewCreated() throws Exception { + } + +} \ No newline at end of file diff --git a/syski_client_android/app/src/androidTest/java/uk/co/syski/client/android/view/fragment/OverviewFragmentTest.java b/syski_client_android/app/src/androidTest/java/uk/co/syski/client/android/view/fragment/OverviewFragmentTest.java new file mode 100644 index 0000000..05d55a8 --- /dev/null +++ b/syski_client_android/app/src/androidTest/java/uk/co/syski/client/android/view/fragment/OverviewFragmentTest.java @@ -0,0 +1,20 @@ +package uk.co.syski.client.android.view.fragment; + +import org.junit.Test; + +import static org.junit.Assert.*; + +public class OverviewFragmentTest { + @Test + public void newInstance() throws Exception { + } + + @Test + public void onCreateView() throws Exception { + } + + @Test + public void onViewCreated() throws Exception { + } + +} \ No newline at end of file diff --git a/syski_client_android/app/src/androidTest/java/uk/co/syski/client/android/view/graph/VariableCPULoadGraphTest.java b/syski_client_android/app/src/androidTest/java/uk/co/syski/client/android/view/graph/VariableCPULoadGraphTest.java new file mode 100644 index 0000000..eb3e9aa --- /dev/null +++ b/syski_client_android/app/src/androidTest/java/uk/co/syski/client/android/view/graph/VariableCPULoadGraphTest.java @@ -0,0 +1,12 @@ +package uk.co.syski.client.android.view.graph; + +import org.junit.Test; + +import static org.junit.Assert.*; + +public class VariableCPULoadGraphTest { + @Test + public void onCreate() throws Exception { + } + +} \ No newline at end of file diff --git a/syski_client_android/app/src/main/AndroidManifest.xml b/syski_client_android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..f9d2ba4 --- /dev/null +++ b/syski_client_android/app/src/main/AndroidManifest.xml @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/FCM/FCMService.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/FCM/FCMService.java new file mode 100644 index 0000000..5862749 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/FCM/FCMService.java @@ -0,0 +1,36 @@ +package uk.co.syski.client.android.FCM; + +import android.util.Log; + +import com.google.firebase.messaging.FirebaseMessagingService; +import com.google.firebase.messaging.RemoteMessage; + +/** + * Service for handling Firebase Cloud Messaging (Notifications) For the App + * Only functions when a message is received from Firebase, which is parsed and handled + */ +public class FCMService extends FirebaseMessagingService { + + private static final String TAG = "FCMService"; + + @Override + public void onMessageReceived(RemoteMessage remoteMessage) { + super.onMessageReceived(remoteMessage); + Log.i(TAG, "Message Received"); + + //Check Message Payload + if(remoteMessage.getData().size() > 0){ + //handle the data message here + } + + //get title and the body + String title = remoteMessage.getNotification().getTitle(); + Log.i(TAG, "Title: "+title); + String body = remoteMessage.getNotification().getBody(); + Log.i(TAG, "Body: "+body); + + // build notification + NotifManager.getInstance(this).displayNotification(title,body); + } + +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/FCM/NotifManager.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/FCM/NotifManager.java new file mode 100644 index 0000000..85d98fc --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/FCM/NotifManager.java @@ -0,0 +1,59 @@ +package uk.co.syski.client.android.FCM; + +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.support.v4.app.NotificationCompat; +import android.util.Log; + +import uk.co.syski.client.android.R; +import uk.co.syski.client.android.view.activity.MainActivity; + +import static android.content.Context.NOTIFICATION_SERVICE; + +/** + * Used to manage Notifications for the app, allowing notifications to be displayed + */ +public class NotifManager { + + private static final String TAG = "NotifManager"; + private Context mContext; + private static NotifManager mInstance; + + private NotifManager(Context context) { + mContext = context; + } + + public static synchronized NotifManager getInstance(Context context) { + if (mInstance == null) { + mInstance = new NotifManager(context); + } + return mInstance; + } + + /** + * Used to display a notification while the application is running + * @param title the title of the notification, appearing on the notification bar + * @param body the body (main text) of the notification + */ + public void displayNotification(String title, String body) { + Log.i(TAG, "Displaying Notification: " +title); + NotificationCompat.Builder mBuilder = + new NotificationCompat.Builder(mContext, "syskinotif") + .setSmallIcon(R.drawable.logo_sys_144) + .setContentTitle(title) + .setContentText(body); + + Intent resultIntent = new Intent(mContext, MainActivity.class); + PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, resultIntent, PendingIntent.FLAG_UPDATE_CURRENT); + mBuilder.setContentIntent(pendingIntent); + NotificationManager mNotifyMgr = + (NotificationManager) mContext.getSystemService(NOTIFICATION_SERVICE); + + if (mNotifyMgr != null) { + mNotifyMgr.notify(1, mBuilder.build()); + } + } + +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/APIPaths.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/APIPaths.java new file mode 100644 index 0000000..0821317 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/APIPaths.java @@ -0,0 +1,20 @@ +package uk.co.syski.client.android.model.api; + +import android.content.Context; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; + +import uk.co.syski.client.android.R; + +public class APIPaths +{ + public static String getURL(Context context) + { + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); + String api_url = sp.getString("pref_api_url", context.getString(R.string.pref_api_url_default)); + String api_port = sp.getString("pref_api_port", context.getString(R.string.pref_api_port_default)); + String api_path = sp.getString("pref_api_path", context.getString(R.string.pref_api_path_default)); + return "https://" + api_url + ":" + api_port + api_path; + } +} + diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/VolleySingleton.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/VolleySingleton.java new file mode 100644 index 0000000..27bcccc --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/VolleySingleton.java @@ -0,0 +1,38 @@ +package uk.co.syski.client.android.model.api; + +import android.content.Context; + +import com.android.volley.Request; +import com.android.volley.RequestQueue; +import com.android.volley.toolbox.Volley; + +public class VolleySingleton { + + private static VolleySingleton mInstance; + private RequestQueue mRequestQueue; + private static Context mCtx; + + private VolleySingleton(Context context) { + mCtx = context; + mRequestQueue = getRequestQueue(); + } + + public static synchronized VolleySingleton getInstance(Context context) { + if (mInstance == null) { + mInstance = new VolleySingleton(context); + } + return mInstance; + } + + public RequestQueue getRequestQueue() { + if (mRequestQueue == null) { + mRequestQueue = Volley.newRequestQueue(mCtx.getApplicationContext()); + } + return mRequestQueue; + } + + public void addToRequestQueue(Request req) { + getRequestQueue().add(req); + } + +} \ No newline at end of file diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/package-info.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/package-info.java new file mode 100644 index 0000000..5dcbe81 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/package-info.java @@ -0,0 +1,7 @@ +/** + * Contains all of the classes for API communication + *

+ * Including API requests and Volley + *

+ */ +package uk.co.syski.client.android.model.api; \ No newline at end of file diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/APIAuthorizationRequest.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/APIAuthorizationRequest.java new file mode 100644 index 0000000..0362f1a --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/APIAuthorizationRequest.java @@ -0,0 +1,30 @@ +package uk.co.syski.client.android.model.api.requests; + +import android.content.Context; +import android.support.annotation.Nullable; + +import com.android.volley.AuthFailureError; +import com.android.volley.Response; + +import java.util.HashMap; +import java.util.Map; + +import uk.co.syski.client.android.model.repository.Repository; + +public class APIAuthorizationRequest extends APIRequest { + + private Context mContext; + + public APIAuthorizationRequest(Context context, int method, String url, @Nullable String requestBody, Response.Listener listener, @Nullable Response.ErrorListener errorListener) { + super(context, method, url, requestBody, listener, errorListener); + mContext = context; + } + + @Override + public Map getHeaders() throws AuthFailureError { + Map params = new HashMap(); + params.put("Authorization", "Bearer " + Repository.getInstance().getUserRepository().getUser().AccessToken); + return params; + } + +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/APIRequest.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/APIRequest.java new file mode 100644 index 0000000..8dc29e9 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/APIRequest.java @@ -0,0 +1,23 @@ +package uk.co.syski.client.android.model.api.requests; + +import android.content.Context; +import android.support.annotation.Nullable; + +import com.android.volley.NetworkResponse; +import com.android.volley.Response; +import com.android.volley.toolbox.JsonRequest; + +import uk.co.syski.client.android.model.api.APIPaths; + +public class APIRequest extends JsonRequest { + + public APIRequest(Context context, int method, String url, @Nullable String requestBody, Response.Listener listener, @Nullable Response.ErrorListener errorListener) { + super(method, APIPaths.getURL(context) + url, requestBody, listener, errorListener); + } + + @Override + protected Response parseNetworkResponse(NetworkResponse response) { + return null; + } + +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/auth/APILoginRequest.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/auth/APILoginRequest.java new file mode 100644 index 0000000..2340a32 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/auth/APILoginRequest.java @@ -0,0 +1,77 @@ +package uk.co.syski.client.android.model.api.requests.auth; + +import android.content.Context; +import android.support.annotation.Nullable; + +import com.android.volley.NetworkResponse; +import com.android.volley.ParseError; +import com.android.volley.Response; +import com.android.volley.toolbox.HttpHeaderParser; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.UnsupportedEncodingException; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.UUID; + +import uk.co.syski.client.android.R; +import uk.co.syski.client.android.model.api.requests.APIRequest; +import uk.co.syski.client.android.model.database.entity.UserEntity; +import uk.co.syski.client.android.model.repository.Repository; + +public class APILoginRequest extends APIRequest { + + private String mEmail; + private String mPassword; + private Context mContext; + + public APILoginRequest(Context context, String email, String password, Response.Listener listener, @Nullable Response.ErrorListener errorListener) { + super(context, Method.POST, "auth/user/login", null, listener, errorListener); + mContext = context; + mEmail = email; + mPassword = password; + } + + @Override + public byte[] getBody() { + try { + JSONObject jsonBody = null; + try { + jsonBody = new JSONObject(); + jsonBody.put("email", mEmail); + jsonBody.put("password", mPassword); + } catch (JSONException e) { + e.printStackTrace(); + } + String requestBody = jsonBody.toString(); + return requestBody == null ? null : requestBody.getBytes("utf-8"); + } catch (UnsupportedEncodingException uee) { + return null; + } + } + + @Override + protected Response parseNetworkResponse(NetworkResponse response) { + try { + JSONObject jsonObject = new JSONObject(new String(response.data, HttpHeaderParser.parseCharset(response.headers, PROTOCOL_CHARSET))); + UserEntity userEntity = new UserEntity(); + userEntity.Id = UUID.fromString(jsonObject.getString("id")); + userEntity.Email = jsonObject.getString("email"); + userEntity.AccessToken = jsonObject.getString("access_token"); + userEntity.RefreshToken = jsonObject.getString("refresh_token"); + userEntity.TokenExpiry = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSSSSSZ").parse(jsonObject.getString("expiry")); + Repository.getInstance().getUserRepository().insert(userEntity); + Repository.getInstance().getUserRepository().setActiveUserId(userEntity.Id); + mContext.getSharedPreferences(mContext.getString(R.string.preference_usrID_key), Context.MODE_PRIVATE).edit().putString(mContext.getString(R.string.preference_usrID_key), userEntity.Id.toString()).apply(); + return Response.success(jsonObject, HttpHeaderParser.parseCacheHeaders(response)); + } catch (UnsupportedEncodingException e) { + return Response.error(new ParseError(e)); + } catch (JSONException je) { + return Response.error(new ParseError(je)); + } catch (ParseException e) { + return Response.error(new ParseError(e)); + } + } +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/auth/APIRegisterRequest.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/auth/APIRegisterRequest.java new file mode 100644 index 0000000..60a3328 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/auth/APIRegisterRequest.java @@ -0,0 +1,78 @@ +package uk.co.syski.client.android.model.api.requests.auth; + +import android.content.Context; +import android.support.annotation.Nullable; + +import com.android.volley.NetworkResponse; +import com.android.volley.ParseError; +import com.android.volley.Response; +import com.android.volley.toolbox.HttpHeaderParser; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.UnsupportedEncodingException; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.UUID; + +import uk.co.syski.client.android.R; +import uk.co.syski.client.android.model.api.requests.APIRequest; +import uk.co.syski.client.android.model.database.entity.UserEntity; +import uk.co.syski.client.android.model.repository.Repository; + +public class APIRegisterRequest extends APIRequest { + + private String mEmail; + private String mPassword; + private Context mContext; + + public APIRegisterRequest(Context context, String email, String password, Response.Listener listener, @Nullable Response.ErrorListener errorListener) { + super(context, Method.POST, "auth/user/register", null, listener, errorListener); + mContext = context; + mEmail = email; + mPassword = password; + } + + @Override + public byte[] getBody() { + try { + JSONObject jsonBody = null; + try { + jsonBody = new JSONObject(); + jsonBody.put("email", mEmail); + jsonBody.put("password", mPassword); + } catch (JSONException e) { + e.printStackTrace(); + } + String requestBody = jsonBody.toString(); + return requestBody == null ? null : requestBody.getBytes("utf-8"); + } catch (UnsupportedEncodingException uee) { + return null; + } + } + + @Override + protected Response parseNetworkResponse(NetworkResponse response) { + try { + JSONObject jsonObject = new JSONObject(new String(response.data, HttpHeaderParser.parseCharset(response.headers, PROTOCOL_CHARSET))); + UserEntity userEntity = new UserEntity(); + userEntity.Id = UUID.fromString(jsonObject.getString("id")); + userEntity.Email = jsonObject.getString("email"); + userEntity.AccessToken = jsonObject.getString("access_token"); + userEntity.RefreshToken = jsonObject.getString("refresh_token"); + userEntity.TokenExpiry = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSSSSSZ").parse(jsonObject.getString("expiry")); + Repository.getInstance().getUserRepository().insert(userEntity); + Repository.getInstance().getUserRepository().setActiveUserId(userEntity.Id); + mContext.getSharedPreferences(mContext.getString(R.string.preference_usrID_key), Context.MODE_PRIVATE).edit().putString(mContext.getString(R.string.preference_usrID_key), userEntity.Id.toString()).apply(); + return Response.success(jsonObject, HttpHeaderParser.parseCacheHeaders(response)); + } catch (UnsupportedEncodingException e) { + return Response.error(new ParseError(e)); + } catch (JSONException je) { + return Response.error(new ParseError(je)); + } catch (ParseException e) { + return Response.error(new ParseError(e)); + } + } + +} \ No newline at end of file diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/auth/APITokenRequest.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/auth/APITokenRequest.java new file mode 100644 index 0000000..84777ec --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/auth/APITokenRequest.java @@ -0,0 +1,78 @@ +package uk.co.syski.client.android.model.api.requests.auth; + +import android.content.Context; + +import com.android.volley.AuthFailureError; +import com.android.volley.NetworkResponse; +import com.android.volley.ParseError; +import com.android.volley.Response; +import com.android.volley.toolbox.HttpHeaderParser; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.UnsupportedEncodingException; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import uk.co.syski.client.android.model.api.requests.APIRequest; +import uk.co.syski.client.android.model.database.entity.UserEntity; +import uk.co.syski.client.android.model.repository.Repository; + +public class APITokenRequest extends APIRequest { + + private UUID mUUID; + + public APITokenRequest(Context context, UUID userId) { + super(context, Method.POST, "auth/user/token/refresh", null, null, null); + mUUID = userId; + } + + @Override + public Map getHeaders() throws AuthFailureError { + Map params = new HashMap(); + //params.put("User-Agent", "Syski APP"); + params.put("Authorization", "Bearer " + Repository.getInstance().getUserRepository().getUser().AccessToken); + return params; + } + + @Override + public byte[] getBody() { + try { + JSONObject jsonBody = null; + try { + jsonBody = new JSONObject(); + jsonBody.put("refresh_token", Repository.getInstance().getUserRepository().getUser().RefreshToken); + } catch (JSONException e) { + e.printStackTrace(); + } + String requestBody = jsonBody.toString(); + return requestBody == null ? null : requestBody.getBytes("utf-8"); + } catch (UnsupportedEncodingException uee) { + return null; + } + } + + @Override + protected Response parseNetworkResponse(NetworkResponse response) { + try { + JSONObject jsonObject = new JSONObject(new String(response.data, HttpHeaderParser.parseCharset(response.headers, PROTOCOL_CHARSET))); + UserEntity userEntity = Repository.getInstance().getUserRepository().getUser(); + userEntity.AccessToken = jsonObject.getString("access_token"); + userEntity.RefreshToken = jsonObject.getString("refresh_token"); + userEntity.TokenExpiry = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSSSSSZ").parse(jsonObject.getString("expiry")); + Repository.getInstance().getUserRepository().update(userEntity); + return Response.success(jsonObject, HttpHeaderParser.parseCacheHeaders(response)); + } catch (UnsupportedEncodingException e) { + return Response.error(new ParseError(e)); + } catch (JSONException je) { + return Response.error(new ParseError(je)); + } catch (ParseException e) { + return Response.error(new ParseError(e)); + } + } + +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/system/APISystemBIOSRequest.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/system/APISystemBIOSRequest.java new file mode 100644 index 0000000..4d1cef5 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/system/APISystemBIOSRequest.java @@ -0,0 +1,60 @@ +package uk.co.syski.client.android.model.api.requests.system; + +import android.content.Context; + +import com.android.volley.NetworkResponse; +import com.android.volley.ParseError; +import com.android.volley.Request; +import com.android.volley.Response; +import com.android.volley.toolbox.HttpHeaderParser; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.UnsupportedEncodingException; +import java.util.UUID; + +import uk.co.syski.client.android.model.api.requests.APIAuthorizationRequest; +import uk.co.syski.client.android.model.database.entity.BIOSEntity; +import uk.co.syski.client.android.model.database.entity.linking.SystemBIOSEntity; +import uk.co.syski.client.android.model.repository.Repository; + +public class APISystemBIOSRequest extends APIAuthorizationRequest { + + private UUID mSystemId; + + public APISystemBIOSRequest(Context context, UUID systemId) { + super(context, Method.GET, "system/" + systemId.toString() + "/bios", null, null, null); + mSystemId = systemId; + } + + @Override + protected Response parseNetworkResponse(NetworkResponse response) { + try { + JSONObject jsonObject = new JSONObject(new String(response.data, HttpHeaderParser.parseCharset(response.headers, PROTOCOL_CHARSET))); + + BIOSEntity biosEntity = Repository.getInstance().getBIOSRepository().getBIOS(UUID.fromString(jsonObject.getString("id"))); + if (biosEntity == null) { + biosEntity = new BIOSEntity(); + } + biosEntity.Id = UUID.fromString(jsonObject.getString("id")); + biosEntity.ManufacturerName = jsonObject.getString("manufacturerName"); + biosEntity.Caption = jsonObject.getString("caption"); + biosEntity.Version = jsonObject.getString("version"); + biosEntity.Date = jsonObject.getString("date"); + + SystemBIOSEntity systemBIOSEntity = new SystemBIOSEntity(); + systemBIOSEntity.SystemId = mSystemId; + systemBIOSEntity.BIOSId = biosEntity.Id; + + Repository.getInstance().getBIOSRepository().upsert(mSystemId, systemBIOSEntity, biosEntity); + + return Response.success(jsonObject, HttpHeaderParser.parseCacheHeaders(response)); + } catch (JSONException | UnsupportedEncodingException e) { + return Response.error(new ParseError(e)); + } + } + + +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/system/APISystemCPUDataRequest.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/system/APISystemCPUDataRequest.java new file mode 100644 index 0000000..c229e6a --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/system/APISystemCPUDataRequest.java @@ -0,0 +1,39 @@ +package uk.co.syski.client.android.model.api.requests.system; + +import android.content.Context; +import android.support.annotation.Nullable; + +import com.android.volley.NetworkResponse; +import com.android.volley.ParseError; +import com.android.volley.Response; +import com.android.volley.toolbox.HttpHeaderParser; + +import org.json.JSONArray; +import org.json.JSONException; + +import java.io.UnsupportedEncodingException; +import java.util.UUID; + +import uk.co.syski.client.android.model.api.requests.APIAuthorizationRequest; + +public class APISystemCPUDataRequest extends APIAuthorizationRequest +{ + + private UUID mSystemId; + + public APISystemCPUDataRequest(Context context, UUID systemId, Response.Listener listener, @Nullable Response.ErrorListener errorListener) { + super(context, Method.GET, "system/" + systemId.toString() + "/cpu/data", null, listener, errorListener); + mSystemId = systemId; + } + + @Override + protected Response parseNetworkResponse(NetworkResponse response) { + try { + JSONArray jsonArray = new JSONArray(new String(response.data, HttpHeaderParser.parseCharset(response.headers, PROTOCOL_CHARSET))); + return Response.success(jsonArray, HttpHeaderParser.parseCacheHeaders(response)); + } catch (UnsupportedEncodingException | JSONException e) { + return Response.error(new ParseError(e)); + } + } + +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/system/APISystemCPURequest.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/system/APISystemCPURequest.java new file mode 100644 index 0000000..f69c7cc --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/system/APISystemCPURequest.java @@ -0,0 +1,77 @@ +package uk.co.syski.client.android.model.api.requests.system; + +import android.content.Context; + +import com.android.volley.NetworkResponse; +import com.android.volley.ParseError; +import com.android.volley.Response; +import com.android.volley.toolbox.HttpHeaderParser; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.UnsupportedEncodingException; +import java.util.LinkedList; +import java.util.List; +import java.util.UUID; + +import uk.co.syski.client.android.model.api.requests.APIAuthorizationRequest; +import uk.co.syski.client.android.model.database.entity.CPUEntity; +import uk.co.syski.client.android.model.database.entity.linking.SystemCPUEntity; +import uk.co.syski.client.android.model.repository.CPURepository; +import uk.co.syski.client.android.model.repository.Repository; + +public class APISystemCPURequest extends APIAuthorizationRequest { + + private UUID mSystemId; + private CPURepository mCPURepository; + + public APISystemCPURequest(Context context, UUID systemId) { + super(context, Method.GET, "system/" + systemId.toString() + "/cpu", null, null, null); + mSystemId = systemId; + mCPURepository = Repository.getInstance().getCPURepository(); + } + + @Override + protected Response parseNetworkResponse(NetworkResponse response) { + try { + JSONArray jsonArray = new JSONArray(new String(response.data, HttpHeaderParser.parseCharset(response.headers, PROTOCOL_CHARSET))); + + List newCPUEntities = new LinkedList<>(); + List newSystemCPUEntities = new LinkedList<>(); + + for (int i = 0; i < jsonArray.length(); i++) { + + // Load from Database + //CPUEntity cpuEntity = SyskiCache.GetDatabase().CPUDao().get()); + + // Load from Repository (Using Cache) + CPUEntity cpuEntity = Repository.getInstance().getCPURepository().getCPU(UUID.fromString(((JSONObject) jsonArray.get(i)).getString("id"))); + if (cpuEntity == null) + { + cpuEntity = new CPUEntity(); + } + cpuEntity.Id = UUID.fromString(((JSONObject) jsonArray.get(i)).getString("id")); + cpuEntity.ManufacturerName = ((JSONObject) jsonArray.get(i)).getString("manufacturerName"); + cpuEntity.ModelName = ((JSONObject) jsonArray.get(i)).getString("modelName"); + cpuEntity.ArchitectureName = ((JSONObject) jsonArray.get(i)).getString("architectureName"); + cpuEntity.ClockSpeed = (int) Double.parseDouble(((JSONObject) jsonArray.get(i)).getString("clockSpeed")); + cpuEntity.CoreCount = (int) Double.parseDouble(((JSONObject) jsonArray.get(i)).getString("coreCount")); + cpuEntity.ThreadCount = (int) Double.parseDouble(((JSONObject) jsonArray.get(i)).getString("threadCount")); + newCPUEntities.add(cpuEntity); + + SystemCPUEntity systemCPUEntity = new SystemCPUEntity(); + systemCPUEntity.SystemId = mSystemId; + systemCPUEntity.CPUId = cpuEntity.Id; + newSystemCPUEntities.add(systemCPUEntity); + } + Repository.getInstance().getCPURepository().upsert(mSystemId, newSystemCPUEntities, newCPUEntities); + + return Response.success(jsonArray, HttpHeaderParser.parseCacheHeaders(response)); + } catch (UnsupportedEncodingException | JSONException e) { + return Response.error(new ParseError(e)); + } + } + +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/system/APISystemGPURequest.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/system/APISystemGPURequest.java new file mode 100644 index 0000000..c9ffcc4 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/system/APISystemGPURequest.java @@ -0,0 +1,69 @@ +package uk.co.syski.client.android.model.api.requests.system; + +import android.content.Context; + +import com.android.volley.NetworkResponse; +import com.android.volley.ParseError; +import com.android.volley.Response; +import com.android.volley.toolbox.HttpHeaderParser; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.UnsupportedEncodingException; +import java.util.LinkedList; +import java.util.List; +import java.util.UUID; + +import uk.co.syski.client.android.model.api.requests.APIAuthorizationRequest; +import uk.co.syski.client.android.model.database.entity.GPUEntity; +import uk.co.syski.client.android.model.database.entity.linking.SystemGPUEntity; +import uk.co.syski.client.android.model.repository.Repository; + +public class APISystemGPURequest extends APIAuthorizationRequest { + + private UUID mSystemId; + + public APISystemGPURequest(Context context, UUID systemId) { + super(context, Method.GET, "system/" + systemId.toString() + "/gpu", null, null, null); + mSystemId = systemId; + } + + @Override + protected Response parseNetworkResponse(NetworkResponse response) { + try { + + JSONArray jsonArray = new JSONArray(new String(response.data, HttpHeaderParser.parseCharset(response.headers, PROTOCOL_CHARSET))); + + List newGPUEntities = new LinkedList<>(); + List newSystemGPUEntities = new LinkedList<>(); + for (int i = 0; i < jsonArray.length(); i++) { + + // Load from Database + //GPUEntity gpuEntity = SyskiCache.GetDatabase().GPUDao().get()); + + // Load from Repository (Using Cache) + GPUEntity gpuEntity = Repository.getInstance().getGPURepository().getGPU(UUID.fromString(((JSONObject) jsonArray.get(i)).getString("id"))); + if (gpuEntity == null) { + gpuEntity = new GPUEntity(); + } + gpuEntity.Id = UUID.fromString(((JSONObject) jsonArray.get(i)).getString("id")); + gpuEntity.ManufacturerName = ((JSONObject) jsonArray.get(i)).getString("manufacturerName"); + gpuEntity.ModelName = ((JSONObject) jsonArray.get(i)).getString("modelName"); + newGPUEntities.add(gpuEntity); + + SystemGPUEntity systemGPUEntity = new SystemGPUEntity(); + systemGPUEntity.SystemId = mSystemId; + systemGPUEntity.GPUId = gpuEntity.Id; + newSystemGPUEntities.add(systemGPUEntity); + } + Repository.getInstance().getGPURepository().upsert(mSystemId, newSystemGPUEntities, newGPUEntities); + + return Response.success(jsonArray, HttpHeaderParser.parseCacheHeaders(response)); + } catch (UnsupportedEncodingException | JSONException e) { + return Response.error(new ParseError(e)); + } + } + +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/system/APISystemKillProcess.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/system/APISystemKillProcess.java new file mode 100644 index 0000000..9c8ad72 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/system/APISystemKillProcess.java @@ -0,0 +1,54 @@ +package uk.co.syski.client.android.model.api.requests.system; + +import android.content.Context; + +import com.android.volley.NetworkResponse; +import com.android.volley.ParseError; +import com.android.volley.Response; +import com.android.volley.toolbox.HttpHeaderParser; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.UnsupportedEncodingException; +import java.util.UUID; + +import uk.co.syski.client.android.model.api.requests.APIAuthorizationRequest; + +public class APISystemKillProcess extends APIAuthorizationRequest { + + private int processId; + + public APISystemKillProcess(Context context, UUID systemId, int processId){ + super(context,Method.POST,"system/"+systemId+"/processes/kill",null,null,null); + this.processId = processId; + } + + @Override + public byte[] getBody() { + try { + JSONObject jsonBody = null; + try { + jsonBody = new JSONObject(); + jsonBody.put("id", processId); + } catch (JSONException e) { + e.printStackTrace(); + } + String requestBody = jsonBody.toString(); + return requestBody == null ? null : requestBody.getBytes("utf-8"); + } catch (UnsupportedEncodingException uee) { + return null; + } + } + + @Override + protected Response parseNetworkResponse(NetworkResponse response){ + try{ + JSONObject jsonArray=new JSONObject(new String(response.data, HttpHeaderParser.parseCharset(response.headers,PROTOCOL_CHARSET))); + return Response.success(jsonArray,HttpHeaderParser.parseCacheHeaders(response)); + }catch(UnsupportedEncodingException |JSONException e){ + return Response.error(new ParseError(e)); + } + } + +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/system/APISystemMotherboardRequest.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/system/APISystemMotherboardRequest.java new file mode 100644 index 0000000..d9e304b --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/system/APISystemMotherboardRequest.java @@ -0,0 +1,57 @@ +package uk.co.syski.client.android.model.api.requests.system; + +import android.content.Context; + +import com.android.volley.NetworkResponse; +import com.android.volley.ParseError; +import com.android.volley.Response; +import com.android.volley.toolbox.HttpHeaderParser; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.UnsupportedEncodingException; +import java.util.UUID; + +import uk.co.syski.client.android.model.api.requests.APIAuthorizationRequest; +import uk.co.syski.client.android.model.database.entity.MotherboardEntity; +import uk.co.syski.client.android.model.database.entity.linking.SystemMotherboardEntity; +import uk.co.syski.client.android.model.repository.Repository; + +public class APISystemMotherboardRequest extends APIAuthorizationRequest { + + private UUID mSystemId; + + public APISystemMotherboardRequest(Context context, UUID systemId) { + super(context, Method.GET, "system/" + systemId.toString() + "/motherboard", null, null, null); + mSystemId = systemId; + } + + @Override + protected Response parseNetworkResponse(NetworkResponse response) { + try { + JSONObject jsonObject = new JSONObject(new String(response.data, HttpHeaderParser.parseCharset(response.headers, PROTOCOL_CHARSET))); + + //MotherboardEntity motherboardEntity = SyskiCache.GetDatabase().MotherboardDao().get(UUID.fromString(jsonObject.getString("id"))); + MotherboardEntity motherboardEntity = Repository.getInstance().getMOBORepository().getMotherboard(UUID.fromString(jsonObject.getString("id"))); + if (motherboardEntity == null) { + motherboardEntity = new MotherboardEntity(); + } + motherboardEntity.Id = UUID.fromString(jsonObject.getString("id")); + motherboardEntity.ManufacturerName = jsonObject.getString("manufacturerName"); + motherboardEntity.ModelName = jsonObject.getString("modelName"); + motherboardEntity.Version = jsonObject.getString("version"); + + SystemMotherboardEntity systemMotherboardEntity = new SystemMotherboardEntity(); + systemMotherboardEntity.SystemId = mSystemId; + systemMotherboardEntity.MotherboardId = motherboardEntity.Id; + + Repository.getInstance().getMOBORepository().upsert(mSystemId, systemMotherboardEntity, motherboardEntity); + + return Response.success(jsonObject, HttpHeaderParser.parseCacheHeaders(response)); + } catch (JSONException | UnsupportedEncodingException e) { + return Response.error(new ParseError(e)); + } + } + +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/system/APISystemOperatingSystemRequest.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/system/APISystemOperatingSystemRequest.java new file mode 100644 index 0000000..0462cdf --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/system/APISystemOperatingSystemRequest.java @@ -0,0 +1,66 @@ +package uk.co.syski.client.android.model.api.requests.system; + +import android.content.Context; + +import com.android.volley.NetworkResponse; +import com.android.volley.ParseError; +import com.android.volley.Response; +import com.android.volley.toolbox.HttpHeaderParser; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.UnsupportedEncodingException; +import java.util.LinkedList; +import java.util.List; +import java.util.UUID; + +import uk.co.syski.client.android.model.api.requests.APIAuthorizationRequest; +import uk.co.syski.client.android.model.database.entity.OperatingSystemEntity; +import uk.co.syski.client.android.model.database.entity.linking.SystemOSEntity; +import uk.co.syski.client.android.model.repository.Repository; + +public class APISystemOperatingSystemRequest extends APIAuthorizationRequest { + + private UUID mSystemId; + + public APISystemOperatingSystemRequest(Context context, UUID systemId) { + super(context, Method.GET, "system/" + systemId.toString() + "/operatingsystem", null, null, null); + mSystemId = systemId; + } + + @Override + protected Response parseNetworkResponse(NetworkResponse response) { + try { + JSONArray jsonArray = new JSONArray(new String(response.data, HttpHeaderParser.parseCharset(response.headers, PROTOCOL_CHARSET))); + + List newOSEntities = new LinkedList<>(); + List newSystemOSEntities = new LinkedList<>(); + + for (int i = 0; i < jsonArray.length(); i++) { + //OperatingSystemEntity osEntity = SyskiCache.GetDatabase().OperatingSystemDao().GetOperatingSystems(UUID.fromString(((JSONObject) jsonArray.get(i)).getString("id"))); + OperatingSystemEntity osEntity = Repository.getInstance().getOSRepository().getOS(mSystemId); + if (osEntity == null) { + osEntity = new OperatingSystemEntity(); + } + osEntity.Id = UUID.fromString(((JSONObject) jsonArray.get(i)).getString("id")); + osEntity.Name = ((JSONObject) jsonArray.get(i)).getString("name"); + newOSEntities.add(osEntity); + + SystemOSEntity systemOSEntity = new SystemOSEntity(); + systemOSEntity.SystemId = mSystemId; + systemOSEntity.OSId = osEntity.Id; + systemOSEntity.ArchitectureName = ((JSONObject) jsonArray.get(i)).getString("architectureName"); + systemOSEntity.Version = ((JSONObject) jsonArray.get(i)).getString("version"); + newSystemOSEntities.add(systemOSEntity); + } + Repository.getInstance().getOSRepository().upsert(mSystemId, newSystemOSEntities, newOSEntities); + + return Response.success(jsonArray, HttpHeaderParser.parseCacheHeaders(response)); + } catch (UnsupportedEncodingException | JSONException e) { + return Response.error(new ParseError(e)); + } + } + +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/system/APISystemPingRequest.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/system/APISystemPingRequest.java new file mode 100644 index 0000000..4765fef --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/system/APISystemPingRequest.java @@ -0,0 +1,39 @@ +package uk.co.syski.client.android.model.api.requests.system; + +import android.content.Context; +import android.support.annotation.Nullable; + +import com.android.volley.NetworkResponse; +import com.android.volley.ParseError; +import com.android.volley.Response; +import com.android.volley.toolbox.HttpHeaderParser; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.UnsupportedEncodingException; +import java.util.UUID; + +import uk.co.syski.client.android.model.api.requests.APIAuthorizationRequest; + +public class APISystemPingRequest extends APIAuthorizationRequest { + + private UUID mSystemId; + + public APISystemPingRequest(Context context, UUID systemId, Response.Listener listener, @Nullable Response.ErrorListener errorListener) { + super(context, Method.GET, "system/" + systemId.toString() + "/ping", null, listener, errorListener); + mSystemId = systemId; + } + + @Override + protected Response parseNetworkResponse(NetworkResponse response) { + try { + JSONObject jsonArray = new JSONObject(new String(response.data, HttpHeaderParser.parseCharset(response.headers, PROTOCOL_CHARSET))); + return Response.success(jsonArray, HttpHeaderParser.parseCacheHeaders(response)); + } catch (UnsupportedEncodingException | JSONException e) { + return Response.error(new ParseError(e)); + } + } + + +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/system/APISystemProcessDataRequest.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/system/APISystemProcessDataRequest.java new file mode 100644 index 0000000..295b706 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/system/APISystemProcessDataRequest.java @@ -0,0 +1,39 @@ +package uk.co.syski.client.android.model.api.requests.system; + +import android.content.Context; +import android.support.annotation.Nullable; + +import com.android.volley.NetworkResponse; +import com.android.volley.ParseError; +import com.android.volley.Response; +import com.android.volley.toolbox.HttpHeaderParser; + +import org.json.JSONArray; +import org.json.JSONException; + +import java.io.UnsupportedEncodingException; +import java.util.UUID; + +import uk.co.syski.client.android.model.api.requests.APIAuthorizationRequest; + +public class APISystemProcessDataRequest extends APIAuthorizationRequest +{ + + private UUID mSystemId; + + public APISystemProcessDataRequest(Context context, UUID systemId, Response.Listener listener, @Nullable Response.ErrorListener errorListener) { + super(context, Method.GET, "system/" + systemId.toString() + "/processes", null, listener, errorListener); + mSystemId = systemId; + } + + @Override + protected Response parseNetworkResponse(NetworkResponse response) { + try { + JSONArray jsonArray = new JSONArray(new String(response.data, HttpHeaderParser.parseCharset(response.headers, PROTOCOL_CHARSET))); + return Response.success(jsonArray, HttpHeaderParser.parseCacheHeaders(response)); + } catch (UnsupportedEncodingException | JSONException e) { + return Response.error(new ParseError(e)); + } + } + +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/system/APISystemRAMDataRequest.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/system/APISystemRAMDataRequest.java new file mode 100644 index 0000000..8858ad6 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/system/APISystemRAMDataRequest.java @@ -0,0 +1,39 @@ +package uk.co.syski.client.android.model.api.requests.system; + +import android.content.Context; +import android.support.annotation.Nullable; + +import com.android.volley.NetworkResponse; +import com.android.volley.ParseError; +import com.android.volley.Response; +import com.android.volley.toolbox.HttpHeaderParser; + +import org.json.JSONArray; +import org.json.JSONException; + +import java.io.UnsupportedEncodingException; +import java.util.UUID; + +import uk.co.syski.client.android.model.api.requests.APIAuthorizationRequest; + +public class APISystemRAMDataRequest extends APIAuthorizationRequest +{ + + private UUID mSystemId; + + public APISystemRAMDataRequest(Context context, UUID systemId, Response.Listener listener, @Nullable Response.ErrorListener errorListener) { + super(context, Method.GET, "system/" + systemId.toString() + "/ram/data", null, listener, errorListener); + mSystemId = systemId; + } + + @Override + protected Response parseNetworkResponse(NetworkResponse response) { + try { + JSONArray jsonArray = new JSONArray(new String(response.data, HttpHeaderParser.parseCharset(response.headers, PROTOCOL_CHARSET))); + return Response.success(jsonArray, HttpHeaderParser.parseCacheHeaders(response)); + } catch (UnsupportedEncodingException | JSONException e) { + return Response.error(new ParseError(e)); + } + } + +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/system/APISystemRAMRequest.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/system/APISystemRAMRequest.java new file mode 100644 index 0000000..3207c6e --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/system/APISystemRAMRequest.java @@ -0,0 +1,73 @@ +package uk.co.syski.client.android.model.api.requests.system; + +import android.content.Context; + +import com.android.volley.NetworkResponse; +import com.android.volley.ParseError; +import com.android.volley.Response; +import com.android.volley.toolbox.HttpHeaderParser; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.UnsupportedEncodingException; +import java.util.LinkedList; +import java.util.List; +import java.util.UUID; + +import uk.co.syski.client.android.model.api.requests.APIAuthorizationRequest; +import uk.co.syski.client.android.model.database.entity.RAMEntity; +import uk.co.syski.client.android.model.database.entity.linking.SystemRAMEntity; +import uk.co.syski.client.android.model.repository.Repository; + +public class APISystemRAMRequest extends APIAuthorizationRequest { + + private UUID mSystemId; + + public APISystemRAMRequest(Context context, UUID systemId) { + super(context, Method.GET, "system/" + systemId.toString() + "/ram", null, null, null); + mSystemId = systemId; + } + + @Override + protected Response parseNetworkResponse(NetworkResponse response) { + try { + + JSONArray jsonArray = new JSONArray(new String(response.data, HttpHeaderParser.parseCharset(response.headers, PROTOCOL_CHARSET))); + + List newRAMEntities = new LinkedList<>(); + List newSystemRAMEntities = new LinkedList<>(); + + for (int i = 0; i < jsonArray.length(); i++) { + + // Load from Database + //RAMEntity ramEntity = SyskiCache.GetDatabase().RAMDao().GetRAM(UUID.fromString(((JSONObject) jsonArray.get(i)).getString("id"))); + + // Load from Repository (Using Cache) + RAMEntity ramEntity = Repository.getInstance().getRAMRepository().get(UUID.fromString(((JSONObject) jsonArray.get(i)).getString("id"))); + + if (ramEntity == null) { + ramEntity = new RAMEntity(); + } + ramEntity.Id = UUID.fromString(((JSONObject) jsonArray.get(i)).getString("id")); + ramEntity.ManufacturerName = ((JSONObject) jsonArray.get(i)).getString("manufacturerName"); + ramEntity.ModelName = ((JSONObject) jsonArray.get(i)).getString("modelName"); + ramEntity.MemoryBytes = Long.parseLong(((JSONObject) jsonArray.get(i)).getString("memoryBytes")); + newRAMEntities.add(ramEntity); + + SystemRAMEntity systemRAMEntity = new SystemRAMEntity(); + systemRAMEntity.SystemId = mSystemId; + systemRAMEntity.RAMId = ramEntity.Id; + systemRAMEntity.DimmSlot = i; + newSystemRAMEntities.add(systemRAMEntity); + } + Repository.getInstance().getRAMRepository().upsert(mSystemId, newSystemRAMEntities, newRAMEntities); + + return Response.success(jsonArray, HttpHeaderParser.parseCacheHeaders(response)); + } catch (UnsupportedEncodingException | JSONException e) { + return Response.error(new ParseError(e)); + } + } + +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/system/APISystemRemove.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/system/APISystemRemove.java new file mode 100644 index 0000000..3289a26 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/system/APISystemRemove.java @@ -0,0 +1,34 @@ +package uk.co.syski.client.android.model.api.requests.system; + +import android.content.Context; +import android.util.Log; + +import com.android.volley.NetworkResponse; +import com.android.volley.ParseError; +import com.android.volley.Response; +import com.android.volley.toolbox.HttpHeaderParser; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.UnsupportedEncodingException; +import java.util.UUID; + +import uk.co.syski.client.android.model.api.requests.APIAuthorizationRequest; + +public class APISystemRemove extends APIAuthorizationRequest { + + public APISystemRemove(Context context, UUID systemId) { + super(context, Method.POST, "system/" + systemId + "/remove", null, null, null); + } + + @Override + protected Response parseNetworkResponse(NetworkResponse response) { + try { + JSONObject jsonArray = new JSONObject(new String(response.data, HttpHeaderParser.parseCharset(response.headers, PROTOCOL_CHARSET))); + return Response.success(jsonArray, HttpHeaderParser.parseCacheHeaders(response)); + } catch (UnsupportedEncodingException | JSONException e) { + return Response.error(new ParseError(e)); + } + } +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/system/APISystemRestartRequest.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/system/APISystemRestartRequest.java new file mode 100644 index 0000000..18669ad --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/system/APISystemRestartRequest.java @@ -0,0 +1,34 @@ +package uk.co.syski.client.android.model.api.requests.system; + +import android.content.Context; +import android.util.Log; + +import com.android.volley.NetworkResponse; +import com.android.volley.ParseError; +import com.android.volley.Response; +import com.android.volley.toolbox.HttpHeaderParser; + +import org.json.JSONArray; + +import java.util.UUID; + +import uk.co.syski.client.android.model.api.requests.APIAuthorizationRequest; + +public class APISystemRestartRequest extends APIAuthorizationRequest { + + public APISystemRestartRequest(Context context, UUID systemId) { + super(context, Method.POST, "system/" + systemId + "/restart", null, null, null); + } + + @Override + protected Response parseNetworkResponse(NetworkResponse response) { + if (response.statusCode == 200) { + Log.v("Syski_Restart", "Status Worked!"); + return Response.success(null, HttpHeaderParser.parseCacheHeaders(response)); + } else { + Log.v("Syski_Restart", "Status Didn't Worked!"); + } + return Response.error(new ParseError()); + } + +} \ No newline at end of file diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/system/APISystemShutdownRequest.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/system/APISystemShutdownRequest.java new file mode 100644 index 0000000..760d990 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/system/APISystemShutdownRequest.java @@ -0,0 +1,33 @@ +package uk.co.syski.client.android.model.api.requests.system; + +import android.content.Context; +import android.util.Log; + +import com.android.volley.NetworkResponse; +import com.android.volley.ParseError; +import com.android.volley.Response; +import com.android.volley.toolbox.HttpHeaderParser; + +import org.json.JSONArray; + +import java.util.UUID; + +import uk.co.syski.client.android.model.api.requests.APIAuthorizationRequest; + +public class APISystemShutdownRequest extends APIAuthorizationRequest { + + public APISystemShutdownRequest(Context context, UUID systemId) { + super(context, Method.POST, "system/" + systemId + "/shutdown", null, null, null); + } + + @Override + protected Response parseNetworkResponse(NetworkResponse response) { + if (response.statusCode == 200) { + Log.v("Syski_Shutdown", "Status Worked!"); + return Response.success(null, HttpHeaderParser.parseCacheHeaders(response)); + } else { + Log.v("Syski_Shutdown", "Status Didn't Worked!"); + } + return Response.error(new ParseError()); + } +} \ No newline at end of file diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/system/APISystemStorageDataRequest.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/system/APISystemStorageDataRequest.java new file mode 100644 index 0000000..bead68a --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/system/APISystemStorageDataRequest.java @@ -0,0 +1,39 @@ +package uk.co.syski.client.android.model.api.requests.system; + +import android.content.Context; +import android.support.annotation.Nullable; + +import com.android.volley.NetworkResponse; +import com.android.volley.ParseError; +import com.android.volley.Response; +import com.android.volley.toolbox.HttpHeaderParser; + +import org.json.JSONArray; +import org.json.JSONException; + +import java.io.UnsupportedEncodingException; +import java.util.UUID; + +import uk.co.syski.client.android.model.api.requests.APIAuthorizationRequest; + +public class APISystemStorageDataRequest extends APIAuthorizationRequest +{ + + private UUID mSystemId; + + public APISystemStorageDataRequest(Context context, UUID systemId, Response.Listener listener, @Nullable Response.ErrorListener errorListener) { + super(context, Method.GET, "system/" + systemId.toString() + "/storage/data", null, listener, errorListener); + mSystemId = systemId; + } + + @Override + protected Response parseNetworkResponse(NetworkResponse response) { + try { + JSONArray jsonArray = new JSONArray(new String(response.data, HttpHeaderParser.parseCharset(response.headers, PROTOCOL_CHARSET))); + return Response.success(jsonArray, HttpHeaderParser.parseCacheHeaders(response)); + } catch (UnsupportedEncodingException | JSONException e) { + return Response.error(new ParseError(e)); + } + } + +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/system/APISystemStorageRequest.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/system/APISystemStorageRequest.java new file mode 100644 index 0000000..472e506 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/system/APISystemStorageRequest.java @@ -0,0 +1,69 @@ +package uk.co.syski.client.android.model.api.requests.system; + +import android.content.Context; + +import com.android.volley.NetworkResponse; +import com.android.volley.ParseError; +import com.android.volley.Response; +import com.android.volley.toolbox.HttpHeaderParser; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.UnsupportedEncodingException; +import java.util.LinkedList; +import java.util.List; +import java.util.UUID; + +import uk.co.syski.client.android.model.api.requests.APIAuthorizationRequest; +import uk.co.syski.client.android.model.database.entity.StorageEntity; +import uk.co.syski.client.android.model.database.entity.linking.SystemStorageEntity; +import uk.co.syski.client.android.model.repository.Repository; + +public class APISystemStorageRequest extends APIAuthorizationRequest { + + private UUID mSystemId; + + public APISystemStorageRequest(Context context, UUID systemId) { + super(context, Method.GET, "system/" + systemId.toString() + "/storage", null, null, null); + mSystemId = systemId; + } + + @Override + protected Response parseNetworkResponse(NetworkResponse response) { + try { + + JSONArray jsonArray = new JSONArray(new String(response.data, HttpHeaderParser.parseCharset(response.headers, PROTOCOL_CHARSET))); + List newStorageEntities = new LinkedList<>(); + List newSystemStorageEntities = new LinkedList<>(); + for (int i = 0; i < jsonArray.length(); i++) { + + //StorageEntity storageEntity = SyskiCache.GetDatabase().StorageDao().get()); + + StorageEntity storageEntity = Repository.getInstance().getStorageRepository().get(UUID.fromString(((JSONObject) jsonArray.get(i)).getString("id"))); + if (storageEntity == null) { + storageEntity = new StorageEntity(); + } + storageEntity.Id = UUID.fromString(((JSONObject) jsonArray.get(i)).getString("id")); + storageEntity.ManufacturerName = ((JSONObject) jsonArray.get(i)).getString("manufacturerName"); + storageEntity.ModelName = ((JSONObject) jsonArray.get(i)).getString("modelName"); + storageEntity.MemoryTypeName = ((JSONObject) jsonArray.get(i)).getString("memoryTypeName"); + storageEntity.MemoryBytes = Long.parseLong(((JSONObject) jsonArray.get(i)).getString("memoryBytes")); + newStorageEntities.add(storageEntity); + + SystemStorageEntity systemStorageEntity = new SystemStorageEntity(); + systemStorageEntity.SystemId = mSystemId; + systemStorageEntity.StorageId = storageEntity.Id; + systemStorageEntity.Slot = i; + newSystemStorageEntities.add(systemStorageEntity); + } + Repository.getInstance().getStorageRepository().upsert(mSystemId, newSystemStorageEntities, newStorageEntities); + + return Response.success(jsonArray, HttpHeaderParser.parseCacheHeaders(response)); + } catch (UnsupportedEncodingException | JSONException e) { + return Response.error(new ParseError(e)); + } + } + +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/system/APISystemsRequest.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/system/APISystemsRequest.java new file mode 100644 index 0000000..27842a6 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/api/requests/system/APISystemsRequest.java @@ -0,0 +1,62 @@ +package uk.co.syski.client.android.model.api.requests.system; + +import android.content.Context; + +import com.android.volley.NetworkResponse; +import com.android.volley.ParseError; +import com.android.volley.Response; +import com.android.volley.toolbox.HttpHeaderParser; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.UnsupportedEncodingException; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.UUID; + +import uk.co.syski.client.android.model.api.requests.APIAuthorizationRequest; +import uk.co.syski.client.android.model.database.entity.SystemEntity; +import uk.co.syski.client.android.model.repository.Repository; + +public class APISystemsRequest extends APIAuthorizationRequest { + + public APISystemsRequest(Context context) { + super(context, Method.GET, "system/all", null, null, null); + } + + @Override + protected Response parseNetworkResponse(NetworkResponse response) { + try { + JSONArray jsonArray = new JSONArray(new String(response.data, HttpHeaderParser.parseCharset(response.headers, PROTOCOL_CHARSET))); + + for (int i = 0; i < jsonArray.length(); i++) { + + // Load from Database + //SystemEntity systemEntity = SyskiCache.GetDatabase().SystemDao().get(UUID.fromString(((JSONObject) jsonArray.get(i)).getString("id"))); + + // Load from Repository (Using Cache) + SystemEntity systemEntity = Repository.getInstance().getSystemRepository().get(UUID.fromString(((JSONObject) jsonArray.get(i)).getString("id"))); + + if (systemEntity == null) + { + systemEntity = new SystemEntity(); + } + systemEntity.Id = UUID.fromString(((JSONObject) jsonArray.get(i)).getString("id")); + systemEntity.HostName = ((JSONObject) jsonArray.get(i)).getString("hostName"); + systemEntity.ModelName = ((JSONObject) jsonArray.get(i)).getString("modelName"); + systemEntity.ManufacturerName = ((JSONObject) jsonArray.get(i)).getString("manufacturerName"); + systemEntity.LastUpdated = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSSSSS").parse(((JSONObject) jsonArray.get(i)).getString("lastUpdated")); + Repository.getInstance().getSystemRepository().upsert(systemEntity); + } + + return Response.success(jsonArray, HttpHeaderParser.parseCacheHeaders(response)); + } catch (UnsupportedEncodingException | JSONException e) { + return Response.error(new ParseError(e)); + } catch (ParseException e) { + return Response.error(new ParseError(e)); + } + } + +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/CacheDatabase.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/CacheDatabase.java new file mode 100644 index 0000000..33d663c --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/CacheDatabase.java @@ -0,0 +1,88 @@ +package uk.co.syski.client.android.model.database; + +import android.arch.persistence.room.Database; +import android.arch.persistence.room.RoomDatabase; +import android.arch.persistence.room.TypeConverters; + +import uk.co.syski.client.android.model.database.dao.BIOSDao; +import uk.co.syski.client.android.model.database.dao.CPUDao; +import uk.co.syski.client.android.model.database.dao.GPUDao; +import uk.co.syski.client.android.model.database.dao.MotherboardDao; +import uk.co.syski.client.android.model.database.dao.OperatingSystemDao; +import uk.co.syski.client.android.model.database.dao.RAMDao; +import uk.co.syski.client.android.model.database.dao.StorageDao; +import uk.co.syski.client.android.model.database.dao.SystemDao; +import uk.co.syski.client.android.model.database.dao.TypeDao; +import uk.co.syski.client.android.model.database.dao.UserDao; +import uk.co.syski.client.android.model.database.dao.linking.SystemBIOSDao; +import uk.co.syski.client.android.model.database.dao.linking.SystemCPUDao; +import uk.co.syski.client.android.model.database.dao.linking.SystemGPUDao; +import uk.co.syski.client.android.model.database.dao.linking.SystemMotherboardDao; +import uk.co.syski.client.android.model.database.dao.linking.SystemOSDao; +import uk.co.syski.client.android.model.database.dao.linking.SystemRAMDao; +import uk.co.syski.client.android.model.database.dao.linking.SystemStorageDao; +import uk.co.syski.client.android.model.database.dao.linking.SystemTypeDao; +import uk.co.syski.client.android.model.database.entity.BIOSEntity; +import uk.co.syski.client.android.model.database.entity.CPUEntity; +import uk.co.syski.client.android.model.database.entity.GPUEntity; +import uk.co.syski.client.android.model.database.entity.MotherboardEntity; +import uk.co.syski.client.android.model.database.entity.OperatingSystemEntity; +import uk.co.syski.client.android.model.database.entity.RAMEntity; +import uk.co.syski.client.android.model.database.entity.StorageEntity; +import uk.co.syski.client.android.model.database.entity.SystemEntity; +import uk.co.syski.client.android.model.database.entity.TypeEntity; +import uk.co.syski.client.android.model.database.entity.UserEntity; +import uk.co.syski.client.android.model.database.entity.linking.SystemBIOSEntity; +import uk.co.syski.client.android.model.database.entity.linking.SystemCPUEntity; +import uk.co.syski.client.android.model.database.entity.linking.SystemGPUEntity; +import uk.co.syski.client.android.model.database.entity.linking.SystemMotherboardEntity; +import uk.co.syski.client.android.model.database.entity.linking.SystemOSEntity; +import uk.co.syski.client.android.model.database.entity.linking.SystemRAMEntity; +import uk.co.syski.client.android.model.database.entity.linking.SystemStorageEntity; +import uk.co.syski.client.android.model.database.entity.linking.SystemTypeEntity; + +@Database( + entities = { + UserEntity.class, + SystemEntity.class, + SystemTypeEntity.class, + TypeEntity.class, + CPUEntity.class, + SystemCPUEntity.class, + OperatingSystemEntity.class, + SystemOSEntity.class, + RAMEntity.class, + SystemRAMEntity.class, + GPUEntity.class, + SystemGPUEntity.class, + StorageEntity.class, + SystemStorageEntity.class, + MotherboardEntity.class, + SystemMotherboardEntity.class, + BIOSEntity.class, + SystemBIOSEntity.class + }, + version = 1 +) +@TypeConverters({Converters.class}) +public abstract class CacheDatabase extends RoomDatabase { + public abstract UserDao UserDao(); + public abstract SystemDao SystemDao(); + public abstract SystemTypeDao SystemTypeDao(); + public abstract TypeDao TypeDao(); + public abstract CPUDao CPUDao(); + public abstract SystemCPUDao SystemCPUDao(); + public abstract OperatingSystemDao OperatingSystemDao(); + public abstract SystemOSDao SystemOSDao(); + public abstract RAMDao RAMDao(); + public abstract SystemRAMDao SystemRAMDao(); + public abstract GPUDao GPUDao(); + public abstract SystemGPUDao SystemGPUDao(); + public abstract StorageDao StorageDao(); + public abstract SystemStorageDao SystemStorageDao(); + public abstract MotherboardDao MotherboardDao(); + public abstract SystemMotherboardDao SystemMotherboardDao(); + public abstract BIOSDao BIOSDao(); + public abstract SystemBIOSDao SystemBIOSDao(); +} + diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/Converters.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/Converters.java new file mode 100644 index 0000000..b62c055 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/Converters.java @@ -0,0 +1,28 @@ +package uk.co.syski.client.android.model.database; + +import android.arch.persistence.room.TypeConverter; + +import java.util.Date; +import java.util.UUID; + +public class Converters { + @TypeConverter + public static Date LongToDate(Long Long) { + return Long == null ? null : new Date(Long); + } + + @TypeConverter + public static Long DateToLong(Date Date) { + return Date == null ? null : Date.getTime(); + } + + @TypeConverter + public static String UUIDToString(UUID Id) { + return Id == null ? null : Id.toString(); + } + + @TypeConverter + public static UUID StringToUUID(String String) { + return String == null ? null : UUID.fromString(String); + } +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/SyskiCache.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/SyskiCache.java new file mode 100644 index 0000000..a9f7e89 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/SyskiCache.java @@ -0,0 +1,24 @@ +package uk.co.syski.client.android.model.database; + +import android.arch.persistence.room.Room; +import android.content.Context; + +public class SyskiCache { + + private SyskiCache() { + } + + private static CacheDatabase Database; + + /** + * Only works if the Database does not yet exist. + */ + public static void BuildDatabase(Context Context) { + if (Database == null) + Database = Room.databaseBuilder(Context, CacheDatabase.class, "SyskiCache.db").build(); + } + + public static CacheDatabase GetDatabase() { + return Database; + } +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/dao/BIOSDao.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/dao/BIOSDao.java new file mode 100644 index 0000000..032ffd0 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/dao/BIOSDao.java @@ -0,0 +1,58 @@ +package uk.co.syski.client.android.model.database.dao; + +import android.arch.persistence.room.Dao; +import android.arch.persistence.room.Delete; +import android.arch.persistence.room.Insert; +import android.arch.persistence.room.OnConflictStrategy; +import android.arch.persistence.room.Query; +import android.arch.persistence.room.Update; +import android.database.sqlite.SQLiteConstraintException; + +import java.util.List; +import java.util.UUID; + +import uk.co.syski.client.android.model.database.entity.BIOSEntity; +import uk.co.syski.client.android.model.database.entity.CPUEntity; + +@Dao +public abstract class BIOSDao { + + @Insert(onConflict = OnConflictStrategy.FAIL) + public abstract void insert(BIOSEntity biosEntity); + + @Insert(onConflict = OnConflictStrategy.FAIL) + public abstract void insert(BIOSEntity... biosEntities); + + @Query("SELECT * FROM BIOSEntity") + public abstract List get(); + + @Query("SELECT * FROM BIOSEntity WHERE Id == :Id") + public abstract BIOSEntity get(UUID Id); + + @Query("SELECT * FROM BIOSEntity WHERE Id in (:Ids)") + public abstract List get(UUID... Ids); + + @Query("SELECT * FROM BIOSEntity INNER JOIN SystemBIOSEntity ON Id = BIOSId WHERE SystemId IN (:Ids)") + public abstract List getSystemBIOSs(UUID... Ids); + + @Update(onConflict = OnConflictStrategy.FAIL) + public abstract void update(BIOSEntity biosEntity); + + @Update(onConflict = OnConflictStrategy.FAIL) + public abstract void update(BIOSEntity... biosEntities); + + @Delete + public abstract void delete(BIOSEntity biosEntity); + + @Delete + public abstract void delete(BIOSEntity... biosEntities); + + public void upsert(BIOSEntity biosEntity) { + try { + insert(biosEntity); + } catch (SQLiteConstraintException exception) { + update(biosEntity); + } + } + +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/dao/CPUDao.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/dao/CPUDao.java new file mode 100644 index 0000000..b2ac9b9 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/dao/CPUDao.java @@ -0,0 +1,57 @@ +package uk.co.syski.client.android.model.database.dao; + +import android.arch.persistence.room.Dao; +import android.arch.persistence.room.Delete; +import android.arch.persistence.room.Insert; +import android.arch.persistence.room.OnConflictStrategy; +import android.arch.persistence.room.Query; +import android.arch.persistence.room.Update; +import android.database.sqlite.SQLiteConstraintException; + +import java.util.List; +import java.util.UUID; + +import uk.co.syski.client.android.model.database.entity.CPUEntity; + +@Dao +public abstract class CPUDao { + + @Insert(onConflict = OnConflictStrategy.FAIL) + public abstract void insert(CPUEntity cpuEntity); + + @Insert(onConflict = OnConflictStrategy.FAIL) + public abstract void insert(CPUEntity... cpuEntities); + + @Query("SELECT * FROM CPUEntity") + public abstract List get(); + + @Query("SELECT * FROM CPUEntity WHERE Id == :Id") + public abstract CPUEntity get(UUID Id); + + @Query("SELECT * FROM CPUEntity WHERE Id in (:Ids)") + public abstract List get(UUID... Ids); + + @Query("SELECT * FROM CPUEntity INNER JOIN SystemCPUEntity ON Id = CPUId WHERE SystemId IN (:Ids)") + public abstract List getSystemCPUs(UUID... Ids); + + @Update(onConflict = OnConflictStrategy.FAIL) + public abstract void update(CPUEntity cpuEntity); + + @Update(onConflict = OnConflictStrategy.FAIL) + public abstract void update(CPUEntity... cpuEntities); + + @Delete + public abstract void delete(CPUEntity cpuEntity); + + @Delete + public abstract void delete(CPUEntity... cpuEntities); + + public void upsert(CPUEntity cpuEntitiy) { + try { + insert(cpuEntitiy); + } catch (SQLiteConstraintException exception) { + update(cpuEntitiy); + } + } + +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/dao/GPUDao.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/dao/GPUDao.java new file mode 100644 index 0000000..44aa5d2 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/dao/GPUDao.java @@ -0,0 +1,53 @@ +package uk.co.syski.client.android.model.database.dao; + +import android.arch.persistence.room.Dao; +import android.arch.persistence.room.Delete; +import android.arch.persistence.room.Insert; +import android.arch.persistence.room.Query; +import android.arch.persistence.room.Update; +import android.database.sqlite.SQLiteConstraintException; + +import java.util.List; +import java.util.UUID; + +import uk.co.syski.client.android.model.database.entity.GPUEntity; + +@Dao +public abstract class GPUDao { + + @Insert + public abstract void insert(GPUEntity gpuEntity); + + @Insert + public abstract void insert(GPUEntity... gpuEntities); + + @Query("SELECT * FROM GPUEntity INNER JOIN SystemGPUEntity ON Id = GPUId WHERE SystemId IN (:Ids)") + public abstract List getSystemGPUs(UUID... Ids); + + @Query("SELECT * FROM GPUEntity") + public abstract List get(); + + @Query("SELECT * FROM GPUEntity WHERE Id == :Id") + public abstract GPUEntity get(UUID Id); + + @Update + public abstract void update (GPUEntity gpuEntity); + + @Update + public abstract void update (GPUEntity... gpuEntities); + + @Delete + public abstract void delete(GPUEntity gpuEntity); + + @Delete + public abstract void delete(GPUEntity... gpuEntities); + + public void upsert(GPUEntity gpuEntity) { + try { + insert(gpuEntity); + } catch (SQLiteConstraintException exception) { + update(gpuEntity); + } + } + +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/dao/MotherboardDao.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/dao/MotherboardDao.java new file mode 100644 index 0000000..4807fee --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/dao/MotherboardDao.java @@ -0,0 +1,57 @@ +package uk.co.syski.client.android.model.database.dao; + +import android.arch.persistence.room.Dao; +import android.arch.persistence.room.Delete; +import android.arch.persistence.room.Insert; +import android.arch.persistence.room.OnConflictStrategy; +import android.arch.persistence.room.Query; +import android.arch.persistence.room.Update; +import android.database.sqlite.SQLiteConstraintException; + +import java.util.List; +import java.util.UUID; + +import uk.co.syski.client.android.model.database.entity.MotherboardEntity; + +@Dao +public abstract class MotherboardDao { + + @Insert(onConflict = OnConflictStrategy.FAIL) + public abstract void insert(MotherboardEntity motherboardEntitiy); + + @Insert(onConflict = OnConflictStrategy.FAIL) + public abstract void insert(MotherboardEntity... motherboardEntities); + + @Query("SELECT * FROM MotherboardEntity") + public abstract List get(); + + @Query("SELECT * FROM MotherboardEntity WHERE Id == :Id") + public abstract MotherboardEntity get(UUID Id); + + @Query("SELECT * FROM MotherboardEntity WHERE Id in (:Ids)") + public abstract List get(UUID... Ids); + + @Query("SELECT * FROM MotherboardEntity INNER JOIN SystemMotherboardEntity ON Id = MotherboardId WHERE SystemId IN (:Ids)") + public abstract List getSystemMotherboards(UUID... Ids); + + @Update(onConflict = OnConflictStrategy.FAIL) + public abstract void update(MotherboardEntity motherboardEntities); + + @Update(onConflict = OnConflictStrategy.FAIL) + public abstract void update(MotherboardEntity... cpuEntities); + + @Delete + public abstract void delete(MotherboardEntity motherboardEntity); + + @Delete + public abstract void delete(MotherboardEntity... motherboardEntities); + + public void upsert(MotherboardEntity motherboardEntitiy) { + try { + insert(motherboardEntitiy); + } catch (SQLiteConstraintException exception) { + update(motherboardEntitiy); + } + } + +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/dao/OperatingSystemDao.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/dao/OperatingSystemDao.java new file mode 100644 index 0000000..4677fb1 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/dao/OperatingSystemDao.java @@ -0,0 +1,57 @@ +package uk.co.syski.client.android.model.database.dao; + +import android.arch.persistence.room.Dao; +import android.arch.persistence.room.Delete; +import android.arch.persistence.room.Insert; +import android.arch.persistence.room.OnConflictStrategy; +import android.arch.persistence.room.Query; +import android.arch.persistence.room.Update; +import android.database.sqlite.SQLiteConstraintException; + +import java.util.List; +import java.util.UUID; + +import uk.co.syski.client.android.model.database.entity.OperatingSystemEntity; + +@Dao +public abstract class OperatingSystemDao { + + @Insert(onConflict = OnConflictStrategy.FAIL) + public abstract void insert(OperatingSystemEntity osEntity); + + @Insert(onConflict = OnConflictStrategy.FAIL) + public abstract void insert(OperatingSystemEntity... osEntities); + + @Query("SELECT * FROM OperatingSystemEntity") + public abstract List get(); + + @Query("SELECT * FROM OperatingSystemEntity WHERE Id == :Id") + public abstract OperatingSystemEntity get(UUID Id); + + @Query("SELECT * FROM OperatingSystemEntity WHERE Id in (:Ids)") + public abstract List get(UUID... Ids); + + //@Query("SELECT * FROM OperatingSystemEntity INNER JOIN SystemOSEntity ON Id = OSId WHERE SystemId IN (:Ids)") + //public abstract List getSystemOperatingSystems(UUID... Ids); + + @Update + public abstract void update (OperatingSystemEntity osEntity); + + @Update + public abstract void update (OperatingSystemEntity... osEntities); + + @Delete + public abstract void delete(OperatingSystemEntity osEntity); + + @Delete + public abstract void delete(OperatingSystemEntity... osEntities); + + public void upsert(OperatingSystemEntity osEntity) { + try { + insert(osEntity); + } catch (SQLiteConstraintException exception) { + update(osEntity); + } + } + +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/dao/RAMDao.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/dao/RAMDao.java new file mode 100644 index 0000000..714a201 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/dao/RAMDao.java @@ -0,0 +1,47 @@ +package uk.co.syski.client.android.model.database.dao; + +import android.arch.persistence.room.Dao; +import android.arch.persistence.room.Delete; +import android.arch.persistence.room.Insert; +import android.arch.persistence.room.OnConflictStrategy; +import android.arch.persistence.room.Query; +import android.arch.persistence.room.Update; +import android.database.sqlite.SQLiteConstraintException; + +import java.util.List; +import java.util.UUID; + +import uk.co.syski.client.android.model.database.entity.RAMEntity; + +@Dao +public abstract class RAMDao { + + @Query("SELECT * FROM RAMEntity WHERE Id == :Id") + public abstract RAMEntity get(UUID Id); + + @Query("SELECT * FROM RAMEntity") + public abstract List get(); + + @Query("SELECT Id, ModelName, ManufacturerName, MemoryTypeName, MemoryBytes FROM RAMEntity INNER JOIN SystemRAMEntity ON Id = RAMId WHERE SystemId IN (:Ids)") + public abstract List getSystemRAMs(UUID... Ids); + + @Insert(onConflict = OnConflictStrategy.FAIL) + public abstract void insert(RAMEntity... ramEntities); + + @Update(onConflict = OnConflictStrategy.FAIL) + public abstract void update (RAMEntity... ramEntities); + + @Delete + public abstract void delete(RAMEntity... RAMEntities); + + public void upsert(RAMEntity ramEntity) { + try { + insert(ramEntity); + } catch (SQLiteConstraintException exception) { + update(ramEntity); + } + } + + +} + diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/dao/StorageDao.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/dao/StorageDao.java new file mode 100644 index 0000000..b56c092 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/dao/StorageDao.java @@ -0,0 +1,57 @@ +package uk.co.syski.client.android.model.database.dao; + +import android.arch.persistence.room.Dao; +import android.arch.persistence.room.Delete; +import android.arch.persistence.room.Insert; +import android.arch.persistence.room.OnConflictStrategy; +import android.arch.persistence.room.Query; +import android.arch.persistence.room.Update; +import android.database.sqlite.SQLiteConstraintException; + +import java.util.List; +import java.util.UUID; + +import uk.co.syski.client.android.model.database.entity.StorageEntity; + +@Dao +public abstract class StorageDao { + + @Insert(onConflict = OnConflictStrategy.FAIL) + public abstract void insert(StorageEntity storageEntity); + + @Insert(onConflict = OnConflictStrategy.FAIL) + public abstract void insert(StorageEntity... storageEntities); + + @Query("SELECT * FROM StorageEntity") + public abstract List get(); + + @Query("SELECT * FROM StorageEntity WHERE Id == :Id") + public abstract StorageEntity get(UUID Id); + + @Query("SELECT * FROM StorageEntity WHERE Id in (:Ids)") + public abstract List get(UUID... Ids); + + @Query("SELECT * FROM StorageEntity INNER JOIN SystemStorageEntity ON Id = StorageId WHERE SystemId IN (:Ids)") + public abstract List getSystemStorages(UUID... Ids); + + @Update(onConflict = OnConflictStrategy.FAIL) + public abstract void update (StorageEntity storageEntity); + + @Update(onConflict = OnConflictStrategy.FAIL) + public abstract void update (StorageEntity... storageEntities); + + @Delete + public abstract void delete(StorageEntity storageEntity); + + @Delete + public abstract void delete(StorageEntity... storageEntities); + + public void upsert(StorageEntity storageEntity) { + try { + insert(storageEntity); + } catch (SQLiteConstraintException exception) { + update(storageEntity); + } + } + +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/dao/SystemDao.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/dao/SystemDao.java new file mode 100644 index 0000000..0a632b3 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/dao/SystemDao.java @@ -0,0 +1,54 @@ +package uk.co.syski.client.android.model.database.dao; + +import android.arch.persistence.room.Dao; +import android.arch.persistence.room.Delete; +import android.arch.persistence.room.Insert; +import android.arch.persistence.room.OnConflictStrategy; +import android.arch.persistence.room.Query; +import android.arch.persistence.room.Update; +import android.database.sqlite.SQLiteConstraintException; + +import java.util.List; +import java.util.UUID; + +import uk.co.syski.client.android.model.database.entity.SystemEntity; + +@Dao +public abstract class SystemDao { + + @Insert(onConflict = OnConflictStrategy.FAIL) + public abstract void insert(SystemEntity systemEntity); + + @Insert(onConflict = OnConflictStrategy.FAIL) + public abstract void insert(SystemEntity... systemEntities); + + @Query("SELECT * FROM SystemEntity") + public abstract List get(); + + @Query("SELECT * FROM SystemEntity WHERE Id = :uuid") + public abstract SystemEntity get(UUID uuid); + + @Query("SELECT * FROM SystemEntity WHERE Id IN (:Ids)") + public abstract List get(UUID... Ids); + + @Update(onConflict = OnConflictStrategy.FAIL) + public abstract void update (SystemEntity systemEntity); + + @Update(onConflict = OnConflictStrategy.FAIL) + public abstract void update (SystemEntity... systemEntities); + + @Delete + public abstract void delete(SystemEntity systemEntity); + + @Delete + public abstract void delete(SystemEntity... systemEntities); + + public void upsert(SystemEntity systemEntity) { + try { + insert(systemEntity); + } catch (SQLiteConstraintException exception) { + update(systemEntity); + } + } + +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/dao/TypeDao.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/dao/TypeDao.java new file mode 100644 index 0000000..a243640 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/dao/TypeDao.java @@ -0,0 +1,20 @@ +package uk.co.syski.client.android.model.database.dao; + +import java.util.List; +import java.util.UUID; +import android.arch.persistence.room.*; + +import uk.co.syski.client.android.model.database.entity.TypeEntity; + +@Dao +public interface TypeDao { + @Query("SELECT Name FROM TypeEntity INNER JOIN SystemTypeEntity WHERE SystemId IN (:Ids)") + List getTypeNames(UUID... Ids); + + @Insert + void InsertAll(TypeEntity... typeEntities); + + @Delete + void DeleteAll(TypeEntity... typeEntities); + +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/dao/UserDao.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/dao/UserDao.java new file mode 100644 index 0000000..4abb5bb --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/dao/UserDao.java @@ -0,0 +1,43 @@ +package uk.co.syski.client.android.model.database.dao; + +import android.arch.persistence.room.Dao; +import android.arch.persistence.room.Delete; +import android.arch.persistence.room.Insert; +import android.arch.persistence.room.Query; +import android.arch.persistence.room.Update; + +import java.util.List; +import java.util.UUID; + +import uk.co.syski.client.android.model.database.entity.UserEntity; + +@Dao +public interface UserDao { + + @Insert + void insert(UserEntity UserEntity); + + @Insert + void insert(UserEntity... userEntities); + + @Query("SELECT * FROM UserEntity") + List get(); + + @Query("SELECT * FROM UserEntity WHERE Id == :Id") + UserEntity get(UUID Id); + + @Query("SELECT * FROM UserEntity WHERE Id in (:Ids)") + List get(UUID... Ids); + + @Update + void update (UserEntity UserEntity); + + @Update + void update (UserEntity... userEntities); + + @Delete + void delete(UserEntity UserEntity); + + @Delete + void delete(UserEntity... userEntities); +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/dao/linking/SystemBIOSDao.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/dao/linking/SystemBIOSDao.java new file mode 100644 index 0000000..146a3e4 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/dao/linking/SystemBIOSDao.java @@ -0,0 +1,41 @@ +package uk.co.syski.client.android.model.database.dao.linking; + +import android.arch.persistence.room.Dao; +import android.arch.persistence.room.Delete; +import android.arch.persistence.room.Insert; +import android.arch.persistence.room.Query; +import android.arch.persistence.room.Update; + +import java.util.List; +import java.util.UUID; + +import uk.co.syski.client.android.model.database.entity.linking.SystemBIOSEntity; + +@Dao +public interface SystemBIOSDao { + + @Insert + void insert(SystemBIOSEntity... systemBIOSEntities); + + @Insert + void insert(SystemBIOSEntity systemBIOSEntity); + + @Query("SELECT * FROM SystemBIOSEntity") + List get(); + + @Update + void update(SystemBIOSEntity... systemBIOSEntities); + + @Update + void update(SystemBIOSEntity systemBIOSEntity); + + @Query("DELETE FROM SystemBIOSEntity WHERE SystemId = :systemId") + void deleteBySystemId(UUID systemId); + + @Delete + void delete(SystemBIOSEntity... systemBIOSEntities); + + @Delete + void delete(SystemBIOSEntity systemBIOSEntity); + +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/dao/linking/SystemCPUDao.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/dao/linking/SystemCPUDao.java new file mode 100644 index 0000000..2c18218 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/dao/linking/SystemCPUDao.java @@ -0,0 +1,41 @@ +package uk.co.syski.client.android.model.database.dao.linking; + +import android.arch.persistence.room.Dao; +import android.arch.persistence.room.Delete; +import android.arch.persistence.room.Insert; +import android.arch.persistence.room.Query; +import android.arch.persistence.room.Update; + +import java.util.List; +import java.util.UUID; + +import uk.co.syski.client.android.model.database.entity.linking.SystemCPUEntity; + +@Dao +public interface SystemCPUDao { + + @Insert + void insert(SystemCPUEntity... systemCPUEntities); + + @Insert + void insert(SystemCPUEntity systemCPUEntity); + + @Query("SELECT * FROM SystemCPUEntity") + List get(); + + @Update + void update(SystemCPUEntity... systemCPUEntities); + + @Update + void update(SystemCPUEntity systemCPUEntity); + + @Query("DELETE FROM SystemCPUEntity WHERE SystemId = :systemId") + void deleteBySystemId(UUID systemId); + + @Delete + void delete(SystemCPUEntity... systemCPUEntities); + + @Delete + void delete(SystemCPUEntity systemCPUEntity); + +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/dao/linking/SystemGPUDao.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/dao/linking/SystemGPUDao.java new file mode 100644 index 0000000..e662142 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/dao/linking/SystemGPUDao.java @@ -0,0 +1,41 @@ +package uk.co.syski.client.android.model.database.dao.linking; + +import android.arch.persistence.room.Dao; +import android.arch.persistence.room.Delete; +import android.arch.persistence.room.Insert; +import android.arch.persistence.room.Query; +import android.arch.persistence.room.Update; + +import java.util.List; +import java.util.UUID; + +import uk.co.syski.client.android.model.database.entity.linking.SystemGPUEntity; + +@Dao +public interface SystemGPUDao { + + @Insert + void insert(SystemGPUEntity... systemGPUEntities); + + @Insert + void insert(SystemGPUEntity systemGPUEntity); + + @Query("SELECT * FROM SystemGPUEntity") + List get(); + + @Update + void update(SystemGPUEntity... systemGPUEntities); + + @Update + void update(SystemGPUEntity systemGPUEntity); + + @Query("DELETE FROM SystemGPUEntity WHERE SystemId = :systemId") + void deleteBySystemId(UUID systemId); + + @Delete + void delete(SystemGPUEntity... systemGPUEntities); + + @Delete + void delete(SystemGPUEntity systemGPUEntity); + +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/dao/linking/SystemMotherboardDao.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/dao/linking/SystemMotherboardDao.java new file mode 100644 index 0000000..85a2d8c --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/dao/linking/SystemMotherboardDao.java @@ -0,0 +1,41 @@ +package uk.co.syski.client.android.model.database.dao.linking; + +import android.arch.persistence.room.Dao; +import android.arch.persistence.room.Delete; +import android.arch.persistence.room.Insert; +import android.arch.persistence.room.Query; +import android.arch.persistence.room.Update; + +import java.util.List; +import java.util.UUID; + +import uk.co.syski.client.android.model.database.entity.linking.SystemMotherboardEntity; + +@Dao +public interface SystemMotherboardDao { + + @Insert + void insert(SystemMotherboardEntity... systemMotherboardEntities); + + @Insert + void insert(SystemMotherboardEntity systemMotherboardEntity); + + @Query("SELECT * FROM SystemMotherboardEntity") + List get(); + + @Update + void update(SystemMotherboardEntity... systemMotherboardEntities); + + @Update + void update(SystemMotherboardEntity systemMotherboardEntity); + + @Query("DELETE FROM SystemMotherboardEntity WHERE SystemId = :systemId") + void deleteBySystemId(UUID systemId); + + @Delete + void delete(SystemMotherboardEntity... systemMotherboardEntities); + + @Delete + void delete(SystemMotherboardEntity systemMotherboardEntity); + +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/dao/linking/SystemOSDao.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/dao/linking/SystemOSDao.java new file mode 100644 index 0000000..d003266 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/dao/linking/SystemOSDao.java @@ -0,0 +1,41 @@ +package uk.co.syski.client.android.model.database.dao.linking; + +import android.arch.persistence.room.Dao; +import android.arch.persistence.room.Delete; +import android.arch.persistence.room.Insert; +import android.arch.persistence.room.Query; +import android.arch.persistence.room.Update; + +import java.util.List; +import java.util.UUID; + +import uk.co.syski.client.android.model.database.entity.linking.SystemOSEntity; + +@Dao +public interface SystemOSDao { + + @Insert + void insert(SystemOSEntity... systemOSEntities); + + @Insert + void insert(SystemOSEntity systemOSEntity); + + @Query("SELECT * FROM SystemOSEntity") + List get(); + + @Update + void update(SystemOSEntity... systemOSEntities); + + @Update + void update(SystemOSEntity systemOSEntity); + + @Query("DELETE FROM SystemOSEntity WHERE SystemId = :systemId") + void deleteBySystemId(UUID systemId); + + @Delete + void delete(SystemOSEntity... systemOSEntities); + + @Delete + void delete(SystemOSEntity systemOSEntity); + +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/dao/linking/SystemRAMDao.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/dao/linking/SystemRAMDao.java new file mode 100644 index 0000000..157428c --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/dao/linking/SystemRAMDao.java @@ -0,0 +1,41 @@ +package uk.co.syski.client.android.model.database.dao.linking; + +import android.arch.persistence.room.Dao; +import android.arch.persistence.room.Delete; +import android.arch.persistence.room.Insert; +import android.arch.persistence.room.Query; +import android.arch.persistence.room.Update; + +import java.util.List; +import java.util.UUID; + +import uk.co.syski.client.android.model.database.entity.linking.SystemRAMEntity; + +@Dao +public interface SystemRAMDao { + + @Insert + void insert(SystemRAMEntity... systemRAMEntities); + + @Insert + void insert(SystemRAMEntity systemCPUEntity); + + @Query("SELECT * FROM SystemRAMEntity") + List get(); + + @Update + void update(SystemRAMEntity... systemRAMEntities); + + @Update + void update(SystemRAMEntity systemRAMEntity); + + @Query("DELETE FROM SystemRAMEntity WHERE SystemId = :systemId") + void deleteBySystemId(UUID systemId); + + @Delete + void delete(SystemRAMEntity... systemRAMEntities); + + @Delete + void delete(SystemRAMEntity systemRAMEntity); + +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/dao/linking/SystemStorageDao.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/dao/linking/SystemStorageDao.java new file mode 100644 index 0000000..dea5438 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/dao/linking/SystemStorageDao.java @@ -0,0 +1,41 @@ +package uk.co.syski.client.android.model.database.dao.linking; + +import android.arch.persistence.room.Dao; +import android.arch.persistence.room.Delete; +import android.arch.persistence.room.Insert; +import android.arch.persistence.room.Query; +import android.arch.persistence.room.Update; + +import java.util.List; +import java.util.UUID; + +import uk.co.syski.client.android.model.database.entity.linking.SystemStorageEntity; + +@Dao +public interface SystemStorageDao { + + @Insert + void insert(SystemStorageEntity... systemStorageEntities); + + @Insert + void insert(SystemStorageEntity systemStorageEntity); + + @Query("SELECT * FROM SystemStorageEntity") + List get(); + + @Update + void update(SystemStorageEntity... systemStorageEntities); + + @Update + void update(SystemStorageEntity systemStorageEntity); + + @Query("DELETE FROM SystemStorageEntity WHERE SystemId = :systemId") + void deleteBySystemId(UUID systemId); + + @Delete + void delete(SystemStorageEntity... systemStorageEntities); + + @Delete + void delete(SystemStorageEntity systemStorageEntity); + +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/dao/linking/SystemTypeDao.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/dao/linking/SystemTypeDao.java new file mode 100644 index 0000000..c4e65b6 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/dao/linking/SystemTypeDao.java @@ -0,0 +1,11 @@ +package uk.co.syski.client.android.model.database.dao.linking; + +import android.arch.persistence.room.*; + +import uk.co.syski.client.android.model.database.entity.linking.SystemTypeEntity; + +@Dao +public interface SystemTypeDao { + @Insert + void InsertAll(SystemTypeEntity... systemTypeEntities); +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/BIOSEntity.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/BIOSEntity.java new file mode 100644 index 0000000..584990e --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/BIOSEntity.java @@ -0,0 +1,23 @@ +package uk.co.syski.client.android.model.database.entity; + +import android.arch.persistence.room.Entity; +import android.arch.persistence.room.PrimaryKey; +import android.support.annotation.NonNull; + +import java.util.UUID; + +@Entity +public class BIOSEntity +{ + @PrimaryKey + @NonNull + public UUID Id; + + public String ManufacturerName; + + public String Caption; + + public String Version; + + public String Date; +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/CPUEntity.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/CPUEntity.java new file mode 100644 index 0000000..a64e2e4 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/CPUEntity.java @@ -0,0 +1,25 @@ +package uk.co.syski.client.android.model.database.entity; + +import android.arch.persistence.room.Entity; +import android.arch.persistence.room.PrimaryKey; +import android.support.annotation.NonNull; + +import java.util.UUID; + +@Entity +public class CPUEntity { + @PrimaryKey @NonNull + public UUID Id; + + public String ModelName; + + public String ManufacturerName; + + public String ArchitectureName; + + public int ClockSpeed; + + public int CoreCount; + + public int ThreadCount; +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/GPUEntity.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/GPUEntity.java new file mode 100644 index 0000000..1973473 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/GPUEntity.java @@ -0,0 +1,17 @@ +package uk.co.syski.client.android.model.database.entity; + +import android.arch.persistence.room.Entity; +import android.arch.persistence.room.PrimaryKey; +import android.support.annotation.NonNull; + +import java.util.UUID; + +@Entity +public class GPUEntity { + @PrimaryKey @NonNull + public UUID Id; + + public String ModelName; + + public String ManufacturerName; +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/MotherboardEntity.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/MotherboardEntity.java new file mode 100644 index 0000000..485577c --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/MotherboardEntity.java @@ -0,0 +1,19 @@ +package uk.co.syski.client.android.model.database.entity; + +import android.arch.persistence.room.Entity; +import android.arch.persistence.room.PrimaryKey; +import android.support.annotation.NonNull; + +import java.util.UUID; + +@Entity +public class MotherboardEntity { + @PrimaryKey @NonNull + public UUID Id; + + public String ModelName; + + public String ManufacturerName; + + public String Version; +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/OperatingSystemEntity.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/OperatingSystemEntity.java new file mode 100644 index 0000000..4d4328d --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/OperatingSystemEntity.java @@ -0,0 +1,17 @@ +package uk.co.syski.client.android.model.database.entity; + +import android.arch.persistence.room.Entity; +import android.arch.persistence.room.PrimaryKey; +import android.support.annotation.NonNull; + +import java.util.UUID; + +@Entity +public class OperatingSystemEntity { + @PrimaryKey + @NonNull + public UUID Id; + + public String Name; +} + diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/RAMEntity.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/RAMEntity.java new file mode 100644 index 0000000..3bbfe9d --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/RAMEntity.java @@ -0,0 +1,22 @@ +package uk.co.syski.client.android.model.database.entity; + +import android.arch.persistence.room.Entity; +import android.arch.persistence.room.PrimaryKey; +import android.support.annotation.NonNull; + +import java.util.UUID; + +@Entity +public class RAMEntity { + @PrimaryKey @NonNull + public UUID Id; + + public String ModelName; + + public String ManufacturerName; + + public String MemoryTypeName; + + public long MemoryBytes; +} + diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/StorageEntity.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/StorageEntity.java new file mode 100644 index 0000000..9da3121 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/StorageEntity.java @@ -0,0 +1,21 @@ +package uk.co.syski.client.android.model.database.entity; + +import android.arch.persistence.room.Entity; +import android.arch.persistence.room.PrimaryKey; +import android.support.annotation.NonNull; + +import java.util.UUID; + +@Entity +public class StorageEntity { + @PrimaryKey @NonNull + public UUID Id; + + public String ModelName; + + public String ManufacturerName; + + public String MemoryTypeName; + + public long MemoryBytes; +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/SystemEntity.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/SystemEntity.java new file mode 100644 index 0000000..7e3440e --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/SystemEntity.java @@ -0,0 +1,23 @@ +package uk.co.syski.client.android.model.database.entity; + +import android.arch.persistence.room.Entity; +import android.arch.persistence.room.PrimaryKey; +import android.support.annotation.NonNull; + +import java.util.Date; +import java.util.UUID; + +@Entity +public class SystemEntity { + @PrimaryKey + @NonNull + public UUID Id; + + public String HostName; + + public String ModelName; + + public String ManufacturerName; + + public Date LastUpdated; +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/TypeEntity.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/TypeEntity.java new file mode 100644 index 0000000..b8ca527 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/TypeEntity.java @@ -0,0 +1,13 @@ +package uk.co.syski.client.android.model.database.entity; + +import java.util.UUID; +import android.arch.persistence.room.*; +import android.support.annotation.NonNull; + +@Entity +public class TypeEntity { + @PrimaryKey @NonNull + public UUID Id; + + public String Name; +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/UserEntity.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/UserEntity.java new file mode 100644 index 0000000..703c448 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/UserEntity.java @@ -0,0 +1,23 @@ +package uk.co.syski.client.android.model.database.entity; + +import android.arch.persistence.room.Entity; +import android.arch.persistence.room.PrimaryKey; +import android.support.annotation.NonNull; + +import java.util.Date; +import java.util.UUID; + +@Entity +public class UserEntity { + @PrimaryKey + @NonNull + public UUID Id; + + public String Email; + + public String RefreshToken; + + public String AccessToken; + + public Date TokenExpiry; +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/data/CPUDataEntity.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/data/CPUDataEntity.java new file mode 100644 index 0000000..6e68b6e --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/data/CPUDataEntity.java @@ -0,0 +1,40 @@ +package uk.co.syski.client.android.model.database.entity.data; + +import android.arch.persistence.room.Entity; +import android.arch.persistence.room.ForeignKey; +import android.support.annotation.NonNull; + +import java.util.Date; +import java.util.UUID; + +import uk.co.syski.client.android.model.database.entity.SystemEntity; + +import static android.arch.persistence.room.ForeignKey.CASCADE; + +@Entity( + primaryKeys = { + "SystemId", + "CollectionDateTime" + }, + foreignKeys = { + @ForeignKey( + entity = SystemEntity.class, + parentColumns = "Id", + childColumns = "SystemId", + onDelete = CASCADE + ), + } +) +public class CPUDataEntity { + @NonNull + public UUID SystemId; + + @NonNull + public Float Load; + + @NonNull + public Float Processes; + + @NonNull + public Date CollectionDateTime; +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/data/RAMDataEntity.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/data/RAMDataEntity.java new file mode 100644 index 0000000..d88a52f --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/data/RAMDataEntity.java @@ -0,0 +1,37 @@ +package uk.co.syski.client.android.model.database.entity.data; + +import android.arch.persistence.room.Entity; +import android.arch.persistence.room.ForeignKey; +import android.support.annotation.NonNull; + +import java.util.Date; +import java.util.UUID; + +import uk.co.syski.client.android.model.database.entity.SystemEntity; + +import static android.arch.persistence.room.ForeignKey.CASCADE; + +@Entity( + primaryKeys = { + "SystemId", + "CollectionDateTime" + }, + foreignKeys = { + @ForeignKey( + entity = SystemEntity.class, + parentColumns = "Id", + childColumns = "SystemId", + onDelete = CASCADE + ), + } +) +public class RAMDataEntity { + @NonNull + public UUID SystemId; + + @NonNull + public int Free; + + @NonNull + public Date CollectionDateTime; +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/data/StorageDataEntity.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/data/StorageDataEntity.java new file mode 100644 index 0000000..036a154 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/data/StorageDataEntity.java @@ -0,0 +1,52 @@ +package uk.co.syski.client.android.model.database.entity.data; + +import android.arch.persistence.room.Entity; +import android.arch.persistence.room.ForeignKey; +import android.support.annotation.NonNull; + +import java.util.Date; +import java.util.UUID; + +import uk.co.syski.client.android.model.database.entity.SystemEntity; + +import static android.arch.persistence.room.ForeignKey.CASCADE; + +@Entity( + primaryKeys = { + "SystemId", + "CollectionDateTime" + }, + foreignKeys = { + @ForeignKey( + entity = SystemEntity.class, + parentColumns = "Id", + childColumns = "SystemId", + onDelete = CASCADE + ), + } +) +public class StorageDataEntity { + @NonNull + public UUID SystemId; + + @NonNull + public float Time; + + @NonNull + public float Transfers; + + @NonNull + public float Reads; + + @NonNull + public float Writes; + + @NonNull + public float ByteReads; + + @NonNull + public float ByteWrites; + + @NonNull + public Date CollectionDateTime; +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/data/SystemProcessesEntity.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/data/SystemProcessesEntity.java new file mode 100644 index 0000000..642f51d --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/data/SystemProcessesEntity.java @@ -0,0 +1,59 @@ +package uk.co.syski.client.android.model.database.entity.data; + +import android.arch.persistence.room.Entity; +import android.arch.persistence.room.ForeignKey; +import android.support.annotation.NonNull; + +import java.util.Date; +import java.util.UUID; + +import uk.co.syski.client.android.model.database.entity.SystemEntity; + +import static android.arch.persistence.room.ForeignKey.CASCADE; + +@Entity( + primaryKeys = { + "SystemId", + "CollectionDateTime" + }, + foreignKeys = { + @ForeignKey( + entity = SystemEntity.class, + parentColumns = "Id", + childColumns = "SystemId", + onDelete = CASCADE + ), + } +) +public class SystemProcessesEntity { + + @NonNull + public UUID SystemId; + + @NonNull + public int Id; + + @NonNull + public String Name; + + @NonNull + public long MemSize; + + @NonNull + public long KernelTime; + + @NonNull + public String Path; + + @NonNull + public int Threads; + + @NonNull + public long UpTime; + + @NonNull + public int ParentId; + + @NonNull + public Date CollectionDateTime; +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/linking/SystemBIOSEntity.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/linking/SystemBIOSEntity.java new file mode 100644 index 0000000..99e5ef6 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/linking/SystemBIOSEntity.java @@ -0,0 +1,39 @@ +package uk.co.syski.client.android.model.database.entity.linking; + +import android.arch.persistence.room.Entity; +import android.arch.persistence.room.ForeignKey; +import android.support.annotation.NonNull; + +import java.util.UUID; + +import uk.co.syski.client.android.model.database.entity.BIOSEntity; +import uk.co.syski.client.android.model.database.entity.SystemEntity; + +import static android.arch.persistence.room.ForeignKey.CASCADE; + +@Entity( + primaryKeys = { + "SystemId", + }, + foreignKeys = { + @ForeignKey( + entity = SystemEntity.class, + parentColumns = "Id", + childColumns = "SystemId", + onDelete = CASCADE + ), + @ForeignKey( + entity = BIOSEntity.class, + parentColumns = "Id", + childColumns = "BIOSId", + onDelete = CASCADE + ) + } +) +public class SystemBIOSEntity { + @NonNull + public UUID SystemId; + + @NonNull + public UUID BIOSId; +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/linking/SystemCPUEntity.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/linking/SystemCPUEntity.java new file mode 100644 index 0000000..d024cf8 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/linking/SystemCPUEntity.java @@ -0,0 +1,40 @@ +package uk.co.syski.client.android.model.database.entity.linking; + +import android.arch.persistence.room.Entity; +import android.arch.persistence.room.ForeignKey; +import android.support.annotation.NonNull; + +import java.util.UUID; + +import uk.co.syski.client.android.model.database.entity.CPUEntity; +import uk.co.syski.client.android.model.database.entity.SystemEntity; + +import static android.arch.persistence.room.ForeignKey.CASCADE; + +@Entity( + primaryKeys = { + "SystemId", + "CPUId" + }, + foreignKeys = { + @ForeignKey( + entity = SystemEntity.class, + parentColumns = "Id", + childColumns = "SystemId", + onDelete = CASCADE + ), + @ForeignKey( + entity = CPUEntity.class, + parentColumns = "Id", + childColumns = "CPUId", + onDelete = CASCADE + ) + } +) +public class SystemCPUEntity { + @NonNull + public UUID SystemId; + + @NonNull + public UUID CPUId; +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/linking/SystemGPUEntity.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/linking/SystemGPUEntity.java new file mode 100644 index 0000000..e6c5457 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/linking/SystemGPUEntity.java @@ -0,0 +1,40 @@ +package uk.co.syski.client.android.model.database.entity.linking; + +import android.arch.persistence.room.Entity; +import android.arch.persistence.room.ForeignKey; +import android.support.annotation.NonNull; + +import java.util.UUID; + +import uk.co.syski.client.android.model.database.entity.GPUEntity; +import uk.co.syski.client.android.model.database.entity.SystemEntity; + +import static android.arch.persistence.room.ForeignKey.CASCADE; + +@Entity( + primaryKeys = { + "SystemId", + "GPUId" + }, + foreignKeys = { + @ForeignKey( + entity = SystemEntity.class, + parentColumns = "Id", + childColumns = "SystemId", + onDelete = CASCADE + ), + @ForeignKey( + entity = GPUEntity.class, + parentColumns = "Id", + childColumns = "GPUId", + onDelete = CASCADE + ) + } +) +public class SystemGPUEntity { + @NonNull + public UUID SystemId; + + @NonNull + public UUID GPUId; +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/linking/SystemMotherboardEntity.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/linking/SystemMotherboardEntity.java new file mode 100644 index 0000000..6918a19 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/linking/SystemMotherboardEntity.java @@ -0,0 +1,41 @@ +package uk.co.syski.client.android.model.database.entity.linking; + +import android.arch.persistence.room.Entity; +import android.arch.persistence.room.ForeignKey; +import android.support.annotation.NonNull; + +import java.util.UUID; + +import uk.co.syski.client.android.model.database.entity.MotherboardEntity; +import uk.co.syski.client.android.model.database.entity.SystemEntity; + +import static android.arch.persistence.room.ForeignKey.CASCADE; + +@Entity( + primaryKeys = { + "SystemId", + }, + foreignKeys = { + @ForeignKey( + entity = SystemEntity.class, + parentColumns = "Id", + childColumns = "SystemId", + onDelete = CASCADE + ), + @ForeignKey( + entity = MotherboardEntity.class, + parentColumns = "Id", + childColumns = "MotherboardId", + onDelete = CASCADE + ) + } +) +public class SystemMotherboardEntity { + + @NonNull + public UUID SystemId; + + @NonNull + public UUID MotherboardId; + +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/linking/SystemOSEntity.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/linking/SystemOSEntity.java new file mode 100644 index 0000000..f302067 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/linking/SystemOSEntity.java @@ -0,0 +1,44 @@ +package uk.co.syski.client.android.model.database.entity.linking; + +import android.arch.persistence.room.Entity; +import android.arch.persistence.room.ForeignKey; +import android.support.annotation.NonNull; + +import java.util.UUID; + +import uk.co.syski.client.android.model.database.entity.OperatingSystemEntity; +import uk.co.syski.client.android.model.database.entity.SystemEntity; + +import static android.arch.persistence.room.ForeignKey.CASCADE; + +@Entity( + primaryKeys = { + "SystemId", + "OSId" + }, + foreignKeys = { + @ForeignKey( + entity = SystemEntity.class, + parentColumns = "Id", + childColumns = "SystemId", + onDelete = CASCADE + ), + @ForeignKey( + entity = OperatingSystemEntity.class, + parentColumns = "Id", + childColumns = "OSId", + onDelete = CASCADE + ) + } +) +public class SystemOSEntity { + @NonNull + public UUID SystemId; + + @NonNull + public UUID OSId; + + public String ArchitectureName; + + public String Version; +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/linking/SystemRAMEntity.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/linking/SystemRAMEntity.java new file mode 100644 index 0000000..e2e630c --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/linking/SystemRAMEntity.java @@ -0,0 +1,44 @@ +package uk.co.syski.client.android.model.database.entity.linking; + +import android.arch.persistence.room.Entity; +import android.arch.persistence.room.ForeignKey; +import android.support.annotation.NonNull; + +import java.util.UUID; + +import uk.co.syski.client.android.model.database.entity.RAMEntity; +import uk.co.syski.client.android.model.database.entity.SystemEntity; + +import static android.arch.persistence.room.ForeignKey.CASCADE; + +@Entity( + primaryKeys = { + "SystemId", + "RAMId", + "DimmSlot" + }, + foreignKeys = { + @ForeignKey( + entity = SystemEntity.class, + parentColumns = "Id", + childColumns = "SystemId", + onDelete = CASCADE + ), + @ForeignKey( + entity = RAMEntity.class, + parentColumns = "Id", + childColumns = "RAMId", + onDelete = CASCADE + ) + } +) +public class SystemRAMEntity { + @NonNull + public UUID SystemId; + + @NonNull + public UUID RAMId; + + @NonNull + public int DimmSlot; +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/linking/SystemStorageEntity.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/linking/SystemStorageEntity.java new file mode 100644 index 0000000..ae87045 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/linking/SystemStorageEntity.java @@ -0,0 +1,44 @@ +package uk.co.syski.client.android.model.database.entity.linking; + +import android.arch.persistence.room.Entity; +import android.arch.persistence.room.ForeignKey; +import android.support.annotation.NonNull; + +import java.util.UUID; + +import uk.co.syski.client.android.model.database.entity.StorageEntity; +import uk.co.syski.client.android.model.database.entity.SystemEntity; + +import static android.arch.persistence.room.ForeignKey.CASCADE; + +@Entity( + primaryKeys = { + "SystemId", + "StorageId", + "Slot" + }, + foreignKeys = { + @ForeignKey( + entity = SystemEntity.class, + parentColumns = "Id", + childColumns = "SystemId", + onDelete = CASCADE + ), + @ForeignKey( + entity = StorageEntity.class, + parentColumns = "Id", + childColumns = "StorageId", + onDelete = CASCADE + ) + } +) +public class SystemStorageEntity { + @NonNull + public UUID SystemId; + + @NonNull + public UUID StorageId; + + @NonNull + public int Slot; +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/linking/SystemTypeEntity.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/linking/SystemTypeEntity.java new file mode 100644 index 0000000..10a65c0 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/entity/linking/SystemTypeEntity.java @@ -0,0 +1,40 @@ +package uk.co.syski.client.android.model.database.entity.linking; + +import android.arch.persistence.room.Entity; +import android.arch.persistence.room.ForeignKey; +import android.support.annotation.NonNull; + +import java.util.UUID; + +import uk.co.syski.client.android.model.database.entity.SystemEntity; +import uk.co.syski.client.android.model.database.entity.TypeEntity; + +import static android.arch.persistence.room.ForeignKey.CASCADE; + +@Entity( + primaryKeys = { + "SystemId", + "TypeId" + }, + foreignKeys = { + @ForeignKey( + entity = SystemEntity.class, + parentColumns = "Id", + childColumns = "SystemId", + onDelete = CASCADE + ), + @ForeignKey( + entity = TypeEntity.class, + parentColumns = "Id", + childColumns = "TypeId", + onDelete = CASCADE + ) + } +) +public class SystemTypeEntity { + @NonNull + public UUID SystemId; + + @NonNull + public UUID TypeId; +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/package-info.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/package-info.java new file mode 100644 index 0000000..d4f2b82 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/database/package-info.java @@ -0,0 +1,7 @@ +/** + * Classes used to create the application database using Room + *

+ * Contains Data Access Objects (DAOs) and Database Entities + *

+ */ +package uk.co.syski.client.android.model.database; \ No newline at end of file diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/repository/BIOSRepository.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/repository/BIOSRepository.java new file mode 100644 index 0000000..acaab7d --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/repository/BIOSRepository.java @@ -0,0 +1,195 @@ +package uk.co.syski.client.android.model.repository; + +import android.arch.core.util.Function; +import android.arch.lifecycle.LiveData; +import android.arch.lifecycle.MutableLiveData; +import android.arch.lifecycle.Transformations; +import android.content.Context; +import android.os.AsyncTask; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ExecutionException; + +import uk.co.syski.client.android.model.api.VolleySingleton; +import uk.co.syski.client.android.model.api.requests.system.APISystemBIOSRequest; +import uk.co.syski.client.android.model.database.SyskiCache; +import uk.co.syski.client.android.model.database.dao.BIOSDao; +import uk.co.syski.client.android.model.database.dao.linking.SystemBIOSDao; +import uk.co.syski.client.android.model.database.entity.BIOSEntity; +import uk.co.syski.client.android.model.database.entity.linking.SystemBIOSEntity; +import uk.co.syski.client.android.model.viewmodel.SystemBIOSModel; + +public enum BIOSRepository { + INSTANCE; + + // Database DAO's + private BIOSDao mBIOSDao; + private SystemBIOSDao mSystemBIOSDao; + + // BIOS Cache in Memory + private HashMap mBIOSEntities; + private HashMap mSystemBIOSEntities; + private HashMap mSystemBIOSModels; + + // LiveData + private MutableLiveData> mLiveDataSystemBIOSModels; + + BIOSRepository() { + mBIOSDao = SyskiCache.GetDatabase().BIOSDao(); + mSystemBIOSDao = SyskiCache.GetDatabase().SystemBIOSDao(); + + mBIOSEntities = new HashMap<>(); + mSystemBIOSEntities = new HashMap<>(); + mSystemBIOSModels = new HashMap<>(); + + mLiveDataSystemBIOSModels = new MutableLiveData<>(); + loadFromDatabase(); + } + + private void loadFromDatabase() + { + try { + // Load data from Database for OS's + mBIOSEntities = new loadBIOSEntitiesAsyncTask(mBIOSDao, mBIOSEntities).execute().get(); + + // Load data from Database for System OS's + mSystemBIOSEntities = new loadSystemBIOSEntitiesAsyncTask(mSystemBIOSDao, mSystemBIOSEntities).execute().get(); + + // Set Data in LiveData + for (Map.Entry entry : mSystemBIOSEntities.entrySet()) + { + SystemBIOSEntity systemBIOSEntity = entry.getValue(); + BIOSEntity biosEntity = mBIOSEntities.get(systemBIOSEntity.BIOSId); + mSystemBIOSModels.put(entry.getKey(), new SystemBIOSModel(biosEntity.ManufacturerName, biosEntity.Caption, biosEntity.Version, biosEntity.Date)); + } + mLiveDataSystemBIOSModels.postValue(mSystemBIOSModels); + } catch (InterruptedException | ExecutionException e) { + e.printStackTrace(); + } + } + + private void loadFromAPI(Context context, UUID systemId) + { + //TODO Load from API every user defined time. + VolleySingleton.getInstance(context).addToRequestQueue(new APISystemBIOSRequest(context, systemId)); + } + + public LiveData getSystemBIOSLiveData(final UUID systemId, Context context) + { + loadFromAPI(context, systemId); + return Transformations.map(mLiveDataSystemBIOSModels, new Function, SystemBIOSModel>() { + @Override + public SystemBIOSModel apply(HashMap input) { + SystemBIOSModel moboModel = input.get(systemId); + if (moboModel == null) + { + moboModel = new SystemBIOSModel(); + } + return moboModel; + } + }); + } + + public BIOSEntity getBIOS(UUID id) + { + return mBIOSEntities.get(id); + } + + public void upsert(UUID systemId, SystemBIOSEntity systemBIOSEntity, BIOSEntity biosEntity) + { + try { + new upsertBIOSAsyncTask(mBIOSDao, biosEntity).execute().get(); + new upsertSystemBIOSAsyncTask(mSystemBIOSDao, systemId, systemBIOSEntity).execute().get(); + } catch (InterruptedException | ExecutionException e) { + e.printStackTrace(); + } + mBIOSEntities.put(biosEntity.Id, biosEntity); + mSystemBIOSEntities.put(systemId, systemBIOSEntity); + mSystemBIOSModels.put(systemId, new SystemBIOSModel(biosEntity.ManufacturerName, biosEntity.Caption, biosEntity.Version, biosEntity.Date)); + mLiveDataSystemBIOSModels.postValue(mSystemBIOSModels); + } + + private static class loadBIOSEntitiesAsyncTask extends AsyncTask> { + + private BIOSDao mBIOSDao; + private HashMap mBIOSEntities; + + + loadBIOSEntitiesAsyncTask(BIOSDao motherboardDao, HashMap moboEntities) { + mBIOSDao = motherboardDao; + mBIOSEntities = moboEntities; + } + + protected HashMap doInBackground(final Void... voids) { + for (BIOSEntity motherboardEntity : mBIOSDao.get()) + { + mBIOSEntities.put(motherboardEntity.Id, motherboardEntity); + } + return mBIOSEntities; + } + + } + + private static class loadSystemBIOSEntitiesAsyncTask extends AsyncTask> { + + private SystemBIOSDao mSystemBIOSDao; + private HashMap mSystemBIOSEntities; + + + loadSystemBIOSEntitiesAsyncTask(SystemBIOSDao systemBIOSDao, HashMap systemBIOSEntities) { + mSystemBIOSDao = systemBIOSDao; + mSystemBIOSEntities = systemBIOSEntities; + } + + protected HashMap doInBackground(final Void... voids) { + for (SystemBIOSEntity systemBIOSEntity : mSystemBIOSDao.get()) + { + mSystemBIOSEntities.put(systemBIOSEntity.SystemId, systemBIOSEntity); + } + return mSystemBIOSEntities; + } + + } + + private static class upsertBIOSAsyncTask extends AsyncTask { + + private BIOSDao mBIOSDao; + + private BIOSEntity mBIOSEntity; + + upsertBIOSAsyncTask(BIOSDao motherboardDao, BIOSEntity motherboardEntity) { + mBIOSDao = motherboardDao; + mBIOSEntity = motherboardEntity; + } + + protected Void doInBackground(final Void... voids) { + mBIOSDao.upsert(mBIOSEntity); + return null; + } + + } + + private static class upsertSystemBIOSAsyncTask extends AsyncTask { + + private SystemBIOSDao mSystemBIOSDao; + private UUID mSystemId; + + private SystemBIOSEntity mSystemBIOSEntity; + + upsertSystemBIOSAsyncTask(SystemBIOSDao systemBIOSDao, UUID systemId, SystemBIOSEntity systemBIOSEntity) { + mSystemBIOSDao = systemBIOSDao; + mSystemId = systemId; + mSystemBIOSEntity = systemBIOSEntity; + } + + protected Void doInBackground(final Void... voids) { + mSystemBIOSDao.deleteBySystemId(mSystemId); + mSystemBIOSDao.insert(mSystemBIOSEntity); + return null; + } + + } + +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/repository/CPURepository.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/repository/CPURepository.java new file mode 100644 index 0000000..456c083 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/repository/CPURepository.java @@ -0,0 +1,231 @@ +package uk.co.syski.client.android.model.repository; + +import android.arch.core.util.Function; +import android.arch.lifecycle.LiveData; +import android.arch.lifecycle.MutableLiveData; +import android.arch.lifecycle.Transformations; +import android.content.Context; +import android.os.AsyncTask; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ExecutionException; + +import uk.co.syski.client.android.model.api.VolleySingleton; +import uk.co.syski.client.android.model.api.requests.system.APISystemCPURequest; +import uk.co.syski.client.android.model.database.SyskiCache; +import uk.co.syski.client.android.model.database.dao.CPUDao; +import uk.co.syski.client.android.model.database.dao.linking.SystemCPUDao; +import uk.co.syski.client.android.model.database.entity.CPUEntity; +import uk.co.syski.client.android.model.database.entity.linking.SystemCPUEntity; +import uk.co.syski.client.android.model.viewmodel.SystemCPUModel; + +public enum CPURepository { + INSTANCE; + + // Database DAO's + private CPUDao mCPUDao; + private SystemCPUDao mSystemCPUDao; + + // CPU Cache in Memory + private HashMap mCPUEntities; + private HashMap> mSystemCPUEntities; + private HashMap> mSystemCPUModels; + + // LiveData + private MutableLiveData>> mLiveDataSystemCPUModels; + + CPURepository() { + mCPUDao = SyskiCache.GetDatabase().CPUDao(); + mSystemCPUDao = SyskiCache.GetDatabase().SystemCPUDao(); + + mCPUEntities = new HashMap<>(); + mSystemCPUEntities = new HashMap<>(); + mSystemCPUModels = new HashMap<>(); + + mLiveDataSystemCPUModels = new MutableLiveData<>(); + loadFromDatabase(); + } + + private void loadFromDatabase() + { + try { + // Load data from Database for CPU's + mCPUEntities = new loadCPUEntitiesAsyncTask(mCPUDao, mCPUEntities).execute().get(); + + // Load data from Database for System CPU's + mSystemCPUEntities = new loadSystemCPUEntitiesAsyncTask(mSystemCPUDao, mSystemCPUEntities).execute().get(); + + // Set Data in LiveData + for (Map.Entry> entry : mSystemCPUEntities.entrySet()) + { + List CPUModels = mSystemCPUModels.get(entry.getKey()); + if (CPUModels == null) + { + CPUModels = new LinkedList<>(); + } + for (SystemCPUEntity systemCPUEntity : entry.getValue()) + { + CPUEntity cpuEntity = mCPUEntities.get(systemCPUEntity.CPUId); + CPUModels.add(new SystemCPUModel(cpuEntity.ModelName, cpuEntity.ManufacturerName, cpuEntity.ArchitectureName, cpuEntity.ClockSpeed, cpuEntity.CoreCount, cpuEntity.ThreadCount)); + } + mSystemCPUModels.put(entry.getKey(), CPUModels); + } + mLiveDataSystemCPUModels.postValue(mSystemCPUModels); + } catch (InterruptedException | ExecutionException e) { + e.printStackTrace(); + } + } + + private void loadFromAPI(Context context, UUID systemId) + { + //TODO Load from API every user defined time. + VolleySingleton.getInstance(context).addToRequestQueue(new APISystemCPURequest(context, systemId)); + } + + public LiveData> getSystemCPUsLiveData(final UUID systemId, Context context) + { + loadFromAPI(context, systemId); + return Transformations.map(mLiveDataSystemCPUModels, new Function>, List>() { + @Override + public List apply(HashMap> input) { + List cpuModelList = input.get(systemId); + if (cpuModelList == null) + { + cpuModelList = new LinkedList<>(); + } + if (cpuModelList.isEmpty()) + { + cpuModelList.add(new SystemCPUModel()); + } + return cpuModelList; + } + }); + } + + public List getSystemCPUs(UUID systemId) + { + return mSystemCPUEntities.get(systemId); + } + + public CPUEntity getCPU(UUID id) + { + return mCPUEntities.get(id); + } + + public void upsert(UUID systemId, List systemCPUEntities, List cpuEntities) + { + try { + new upsertCPUAsyncTask(mCPUDao, cpuEntities).execute().get(); + new upsertSystemCPUAsyncTask(mSystemCPUDao, systemId, systemCPUEntities).execute().get(); + } catch (InterruptedException | ExecutionException e) { + e.printStackTrace(); + } + List systemCPUModels = new LinkedList<>(); + for (CPUEntity cpuEntity : cpuEntities) + { + mCPUEntities.put(cpuEntity.Id, cpuEntity); + } + for (SystemCPUEntity systemCPUEntity : systemCPUEntities) + { + CPUEntity cpuEntity = mCPUEntities.get(systemCPUEntity.CPUId); + systemCPUModels.add(new SystemCPUModel(cpuEntity.ModelName, cpuEntity.ManufacturerName, cpuEntity.ArchitectureName, cpuEntity.ClockSpeed, cpuEntity.CoreCount, cpuEntity.ThreadCount)); + } + mSystemCPUEntities.put(systemId, systemCPUEntities); + mSystemCPUModels.put(systemId, systemCPUModels); + mLiveDataSystemCPUModels.postValue(mSystemCPUModels); + } + + private static class loadCPUEntitiesAsyncTask extends AsyncTask> { + + private CPUDao mCPUDao; + private HashMap mCPUEntities; + + loadCPUEntitiesAsyncTask(CPUDao cpuDao, HashMap CPUEntities) { + mCPUDao = cpuDao; + mCPUEntities = CPUEntities; + } + + protected HashMap doInBackground(final Void... voids) { + for (CPUEntity cpuEntity : mCPUDao.get()) + { + mCPUEntities.put(cpuEntity.Id, cpuEntity); + } + return mCPUEntities; + } + + } + + private static class loadSystemCPUEntitiesAsyncTask extends AsyncTask>> { + + private SystemCPUDao mSystemCPUDao; + private HashMap> mSystemCPUEntities; + + loadSystemCPUEntitiesAsyncTask(SystemCPUDao systemCPUDao, HashMap> systemCPUEntities) { + mSystemCPUDao = systemCPUDao; + mSystemCPUEntities = systemCPUEntities; + } + + protected HashMap> doInBackground(final Void... voids) { + for (SystemCPUEntity SystemCPUEntity : mSystemCPUDao.get()) + { + List CPUEntities = mSystemCPUEntities.get(SystemCPUEntity.SystemId); + if (CPUEntities == null) + { + CPUEntities = new LinkedList<>(); + } + CPUEntities.add(SystemCPUEntity); + mSystemCPUEntities.put(SystemCPUEntity.SystemId, CPUEntities); + } + return mSystemCPUEntities; + } + + } + + private static class upsertCPUAsyncTask extends AsyncTask { + + private CPUDao mCPUDao; + + private List mCPUEntities; + + upsertCPUAsyncTask(CPUDao cpuDao, List cpuEntities) { + mCPUDao = cpuDao; + mCPUEntities = cpuEntities; + } + + protected Void doInBackground(final Void... voids) { + for (CPUEntity cpuEntity: mCPUEntities) { + mCPUDao.upsert(cpuEntity); + } + return null; + } + + } + + private static class upsertSystemCPUAsyncTask extends AsyncTask { + + private SystemCPUDao mSystemCPUDao; + private UUID mSystemId; + + private List mSystemCPUEntities; + + upsertSystemCPUAsyncTask(SystemCPUDao systemCPUDao, UUID systemId, List systemCPUEntities) { + mSystemCPUDao = systemCPUDao; + mSystemId = systemId; + mSystemCPUEntities = systemCPUEntities; + } + + protected Void doInBackground(final Void... voids) { + mSystemCPUDao.deleteBySystemId(mSystemId); + for (SystemCPUEntity systemCPUEntity: mSystemCPUEntities) { + mSystemCPUDao.insert(systemCPUEntity); + } + return null; + } + + } + +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/repository/GPURepository.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/repository/GPURepository.java new file mode 100644 index 0000000..6aa8724 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/repository/GPURepository.java @@ -0,0 +1,225 @@ +package uk.co.syski.client.android.model.repository; + +import android.arch.core.util.Function; +import android.arch.lifecycle.LiveData; +import android.arch.lifecycle.MutableLiveData; +import android.arch.lifecycle.Transformations; +import android.content.Context; +import android.os.AsyncTask; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ExecutionException; + +import uk.co.syski.client.android.model.api.VolleySingleton; +import uk.co.syski.client.android.model.api.requests.system.APISystemGPURequest; +import uk.co.syski.client.android.model.database.SyskiCache; +import uk.co.syski.client.android.model.database.dao.GPUDao; +import uk.co.syski.client.android.model.database.dao.linking.SystemGPUDao; +import uk.co.syski.client.android.model.database.entity.GPUEntity; +import uk.co.syski.client.android.model.database.entity.linking.SystemGPUEntity; +import uk.co.syski.client.android.model.viewmodel.SystemGPUModel; + +public enum GPURepository { + INSTANCE; + + // Database DAO's + private GPUDao mGPUDao; + private SystemGPUDao mSystemGPUDao; + + // GPU Cache in Memory + private HashMap mGPUEntities; + private HashMap> mSystemGPUEntities; + private HashMap> mSystemGPUModels; + + private MutableLiveData>> mLiveDataSystemGPUModels; + + GPURepository() { + mGPUDao = SyskiCache.GetDatabase().GPUDao(); + mSystemGPUDao = SyskiCache.GetDatabase().SystemGPUDao(); + + mGPUEntities = new HashMap<>(); + mSystemGPUEntities = new HashMap<>(); + mSystemGPUModels = new HashMap<>(); + + mLiveDataSystemGPUModels = new MutableLiveData<>(); + loadFromDatabase(); + } + + private void loadFromDatabase() + { + try { + // Load data from Database for OS's + mGPUEntities = new loadGPUEntitiesAsyncTask(mGPUDao, mGPUEntities).execute().get(); + + // Load data from Database for System OS's + mSystemGPUEntities = new loadSystemGPUEntitiesAsyncTask(mSystemGPUDao, mSystemGPUEntities).execute().get(); + + // Set Data in LiveData + for (Map.Entry> entry : mSystemGPUEntities.entrySet()) + { + List GPUModels = mSystemGPUModels.get(entry.getKey()); + if (GPUModels == null) + { + GPUModels = new LinkedList<>(); + } + for (SystemGPUEntity systemGPUEntity : entry.getValue()) + { + GPUEntity gpuEntity = mGPUEntities.get(systemGPUEntity.GPUId); + GPUModels.add(new SystemGPUModel(gpuEntity.ModelName, gpuEntity.ManufacturerName)); + } + mSystemGPUModels.put(entry.getKey(), GPUModels); + } + mLiveDataSystemGPUModels.postValue(mSystemGPUModels); + } catch (InterruptedException | ExecutionException e) { + e.printStackTrace(); + } + } + + private void loadFromAPI(Context context, UUID systemId) + { + //TODO Load from API every user defined time. + VolleySingleton.getInstance(context).addToRequestQueue(new APISystemGPURequest(context, systemId)); + } + + public LiveData> getSystemGPUsLiveData(final UUID systemId, Context context) + { + loadFromAPI(context, systemId); + return Transformations.map(mLiveDataSystemGPUModels, new Function>, List>() { + @Override + public List apply(HashMap> input) { + List gpuModelList = input.get(systemId); + if (gpuModelList == null) + { + gpuModelList = new LinkedList<>(); + } + if (gpuModelList.isEmpty()) + { + gpuModelList.add(new SystemGPUModel()); + } + return gpuModelList; + } + }); + } + + public GPUEntity getGPU(UUID id) + { + return mGPUEntities.get(id); + } + + public void upsert(UUID systemId, List systemGPUEntities, List gpuEntities) + { + try { + new upsertGPUAsyncTask(mGPUDao, gpuEntities).execute().get(); + new upsertSystemGPUAsyncTask(mSystemGPUDao, systemId, systemGPUEntities).execute().get(); + } catch (InterruptedException | ExecutionException e) { + e.printStackTrace(); + } + List systemGPUModels = new LinkedList<>(); + for (GPUEntity gpuEntity : gpuEntities) + { + mGPUEntities.put(gpuEntity.Id, gpuEntity); + } + for (SystemGPUEntity systemGPUEntity : systemGPUEntities) + { + GPUEntity gpuEntity = mGPUEntities.get(systemGPUEntity.GPUId); + systemGPUModels.add(new SystemGPUModel(gpuEntity.ModelName, gpuEntity.ManufacturerName)); + } + mSystemGPUEntities.put(systemId, systemGPUEntities); + mSystemGPUModels.put(systemId, systemGPUModels); + mLiveDataSystemGPUModels.postValue(mSystemGPUModels); + } + + private static class loadGPUEntitiesAsyncTask extends AsyncTask> { + + private GPUDao mGPUDao; + private HashMap mGPUEntities; + + + loadGPUEntitiesAsyncTask(GPUDao cpuDao, HashMap GPUEntities) { + mGPUDao = cpuDao; + mGPUEntities = GPUEntities; + } + + protected HashMap doInBackground(final Void... voids) { + for (GPUEntity cpuEntity : mGPUDao.get()) + { + mGPUEntities.put(cpuEntity.Id, cpuEntity); + } + return mGPUEntities; + } + + } + + private static class loadSystemGPUEntitiesAsyncTask extends AsyncTask>> { + + private SystemGPUDao mSystemGPUDao; + private HashMap> mSystemGPUEntities; + + + loadSystemGPUEntitiesAsyncTask(SystemGPUDao systemGPUDao, HashMap> systemGPUEntities) { + mSystemGPUDao = systemGPUDao; + mSystemGPUEntities = systemGPUEntities; + } + + protected HashMap> doInBackground(final Void... voids) { + for (SystemGPUEntity SystemGPUEntity : mSystemGPUDao.get()) + { + List GPUEntities = mSystemGPUEntities.get(SystemGPUEntity.SystemId); + if (GPUEntities == null) + { + GPUEntities = new LinkedList<>(); + } + GPUEntities.add(SystemGPUEntity); + mSystemGPUEntities.put(SystemGPUEntity.SystemId, GPUEntities); + } + return mSystemGPUEntities; + } + + } + + private static class upsertGPUAsyncTask extends AsyncTask { + + private GPUDao mGPUDao; + private List mGPUEntities; + + upsertGPUAsyncTask(GPUDao gpuDao, List gpuEntities) { + mGPUDao = gpuDao; + mGPUEntities = gpuEntities; + } + + protected Void doInBackground(final Void... voids) { + for (GPUEntity gpuEntity: mGPUEntities) { + mGPUDao.upsert(gpuEntity); + } + return null; + } + + } + + private static class upsertSystemGPUAsyncTask extends AsyncTask { + + private SystemGPUDao mSystemGPUDao; + private UUID mSystemId; + private List mSystemGPUEntities; + + upsertSystemGPUAsyncTask(SystemGPUDao systemGPUDao, UUID systemId, List systemGPUEntities) { + mSystemGPUDao = systemGPUDao; + mSystemId = systemId; + mSystemGPUEntities = systemGPUEntities; + } + + protected Void doInBackground(final Void... voids) { + mSystemGPUDao.deleteBySystemId(mSystemId); + for (SystemGPUEntity systemGPUEntity: mSystemGPUEntities) { + mSystemGPUDao.insert(systemGPUEntity); + } + return null; + } + + } + +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/repository/MOBORepository.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/repository/MOBORepository.java new file mode 100644 index 0000000..071d2d9 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/repository/MOBORepository.java @@ -0,0 +1,195 @@ +package uk.co.syski.client.android.model.repository; + +import android.arch.core.util.Function; +import android.arch.lifecycle.LiveData; +import android.arch.lifecycle.MutableLiveData; +import android.arch.lifecycle.Transformations; +import android.content.Context; +import android.os.AsyncTask; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ExecutionException; + +import uk.co.syski.client.android.model.api.VolleySingleton; +import uk.co.syski.client.android.model.api.requests.system.APISystemMotherboardRequest; +import uk.co.syski.client.android.model.database.SyskiCache; +import uk.co.syski.client.android.model.database.dao.MotherboardDao; +import uk.co.syski.client.android.model.database.dao.linking.SystemMotherboardDao; +import uk.co.syski.client.android.model.database.entity.MotherboardEntity; +import uk.co.syski.client.android.model.database.entity.linking.SystemMotherboardEntity; +import uk.co.syski.client.android.model.viewmodel.SystemMotherboardModel; + +public enum MOBORepository { + INSTANCE; + + // Database DAO's + private MotherboardDao mMOBODao; + private SystemMotherboardDao mSystemMOBODao; + + // MOBO Cache in Memory + private HashMap mMOBOEntities; + private HashMap mSystemMOBOEntities; + private HashMap mSystemMOBOModels; + + // LiveData + private MutableLiveData> mLiveDataSystemMOBOModels; + + MOBORepository() { + mMOBODao = SyskiCache.GetDatabase().MotherboardDao(); + mSystemMOBODao = SyskiCache.GetDatabase().SystemMotherboardDao(); + + mMOBOEntities = new HashMap<>(); + mSystemMOBOEntities = new HashMap<>(); + mSystemMOBOModels = new HashMap<>(); + + mLiveDataSystemMOBOModels = new MutableLiveData<>(); + loadFromDatabase(); + } + + private void loadFromDatabase() + { + try { + // Load data from Database for OS's + mMOBOEntities = new loadMOBOEntitiesAsyncTask(mMOBODao, mMOBOEntities).execute().get(); + + // Load data from Database for System OS's + mSystemMOBOEntities = new loadSystemMOBOEntitiesAsyncTask(mSystemMOBODao, mSystemMOBOEntities).execute().get(); + + // Set Data in LiveData + for (Map.Entry entry : mSystemMOBOEntities.entrySet()) + { + SystemMotherboardEntity systemMOBOEntity = entry.getValue(); + MotherboardEntity moboEntity = mMOBOEntities.get(systemMOBOEntity.MotherboardId); + mSystemMOBOModels.put(entry.getKey(), new SystemMotherboardModel(moboEntity.ModelName, moboEntity.ManufacturerName, moboEntity.Version)); + } + mLiveDataSystemMOBOModels.postValue(mSystemMOBOModels); + } catch (InterruptedException | ExecutionException e) { + e.printStackTrace(); + } + } + + private void loadFromAPI(Context context, UUID systemId) + { + //TODO Load from API every user defined time. + VolleySingleton.getInstance(context).addToRequestQueue(new APISystemMotherboardRequest(context, systemId)); + } + + public LiveData getSystemMotherboardLiveData(final UUID systemId, Context context) + { + loadFromAPI(context, systemId); + return Transformations.map(mLiveDataSystemMOBOModels, new Function, SystemMotherboardModel>() { + @Override + public SystemMotherboardModel apply(HashMap input) { + SystemMotherboardModel moboModel = input.get(systemId); + if (moboModel == null) + { + moboModel = new SystemMotherboardModel(); + } + return moboModel; + } + }); + } + + public MotherboardEntity getMotherboard(UUID id) + { + return mMOBOEntities.get(id); + } + + public void upsert(UUID systemId, SystemMotherboardEntity systemMOBOEntity, MotherboardEntity moboEntity) + { + try { + new upsertMOBOAsyncTask(mMOBODao, moboEntity).execute().get(); + new upsertSystemMOBOAsyncTask(mSystemMOBODao, systemId, systemMOBOEntity).execute().get(); + } catch (InterruptedException | ExecutionException e) { + e.printStackTrace(); + } + mMOBOEntities.put(moboEntity.Id, moboEntity); + mSystemMOBOEntities.put(systemId, systemMOBOEntity); + mSystemMOBOModels.put(systemId, new SystemMotherboardModel(moboEntity.ModelName, moboEntity.ManufacturerName, moboEntity.Version)); + mLiveDataSystemMOBOModels.postValue(mSystemMOBOModels); + } + + private static class loadMOBOEntitiesAsyncTask extends AsyncTask> { + + private MotherboardDao mMotherboardDao; + private HashMap mMOBOEntities; + + + loadMOBOEntitiesAsyncTask(MotherboardDao motherboardDao, HashMap moboEntities) { + mMotherboardDao = motherboardDao; + mMOBOEntities = moboEntities; + } + + protected HashMap doInBackground(final Void... voids) { + for (MotherboardEntity motherboardEntity : mMotherboardDao.get()) + { + mMOBOEntities.put(motherboardEntity.Id, motherboardEntity); + } + return mMOBOEntities; + } + + } + + private static class loadSystemMOBOEntitiesAsyncTask extends AsyncTask> { + + private SystemMotherboardDao mSystemMotherboardDao; + private HashMap mSystemMOBOEntities; + + + loadSystemMOBOEntitiesAsyncTask(SystemMotherboardDao systemMotherboardDao, HashMap systemMotherboardEntities) { + mSystemMotherboardDao = systemMotherboardDao; + mSystemMOBOEntities = systemMotherboardEntities; + } + + protected HashMap doInBackground(final Void... voids) { + for (SystemMotherboardEntity systemMotherboardEntity : mSystemMotherboardDao.get()) + { + mSystemMOBOEntities.put(systemMotherboardEntity.SystemId, systemMotherboardEntity); + } + return mSystemMOBOEntities; + } + + } + + private static class upsertMOBOAsyncTask extends AsyncTask { + + private MotherboardDao mMotherboardDao; + + private MotherboardEntity mMotherboardEntity; + + upsertMOBOAsyncTask(MotherboardDao motherboardDao, MotherboardEntity motherboardEntity) { + mMotherboardDao = motherboardDao; + mMotherboardEntity = motherboardEntity; + } + + protected Void doInBackground(final Void... voids) { + mMotherboardDao.upsert(mMotherboardEntity); + return null; + } + + } + + private static class upsertSystemMOBOAsyncTask extends AsyncTask { + + private SystemMotherboardDao mSystemMotherboardDao; + private UUID mSystemId; + + private SystemMotherboardEntity mSystemMotherboardEntity; + + upsertSystemMOBOAsyncTask(SystemMotherboardDao systemMotherboardDao, UUID systemId, SystemMotherboardEntity systemMotherboardEntity) { + mSystemMotherboardDao = systemMotherboardDao; + mSystemId = systemId; + mSystemMotherboardEntity = systemMotherboardEntity; + } + + protected Void doInBackground(final Void... voids) { + mSystemMotherboardDao.deleteBySystemId(mSystemId); + mSystemMotherboardDao.insert(mSystemMotherboardEntity); + return null; + } + + } + +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/repository/OSRepository.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/repository/OSRepository.java new file mode 100644 index 0000000..064168f --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/repository/OSRepository.java @@ -0,0 +1,239 @@ +package uk.co.syski.client.android.model.repository; + +import android.arch.core.util.Function; +import android.arch.lifecycle.LiveData; +import android.arch.lifecycle.MutableLiveData; +import android.arch.lifecycle.Transformations; +import android.content.Context; +import android.os.AsyncTask; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ExecutionException; + +import uk.co.syski.client.android.model.api.VolleySingleton; +import uk.co.syski.client.android.model.api.requests.system.APISystemOperatingSystemRequest; +import uk.co.syski.client.android.model.database.SyskiCache; +import uk.co.syski.client.android.model.database.dao.OperatingSystemDao; +import uk.co.syski.client.android.model.database.dao.linking.SystemOSDao; +import uk.co.syski.client.android.model.database.entity.OperatingSystemEntity; +import uk.co.syski.client.android.model.database.entity.linking.SystemOSEntity; +import uk.co.syski.client.android.model.viewmodel.OperatingSystemModel; + +public enum OSRepository { + INSTANCE; + + // Database DAO's + private OperatingSystemDao mOperatingSystemDao; + private SystemOSDao mSystemOSDao; + + // OS Cache in Memory + private HashMap mOSEntities; + private HashMap> mSystemOSEntities; + private HashMap> mSystemOSModels; + + // LiveData + private MutableLiveData>> mLiveDataSystemOSModels; + + OSRepository() { + mOperatingSystemDao = SyskiCache.GetDatabase().OperatingSystemDao(); + mSystemOSDao = SyskiCache.GetDatabase().SystemOSDao(); + + mOSEntities = new HashMap<>(); + mSystemOSEntities = new HashMap<>(); + mSystemOSModels = new HashMap<>(); + + mLiveDataSystemOSModels = new MutableLiveData<>(); + loadFromDatabase(); + } + + private void loadFromDatabase() + { + try { + // Load data from Database for OS's + mOSEntities = new loadOSEntitiesAsyncTask(mOperatingSystemDao, mOSEntities).execute().get(); + + // Load data from Database for System OS's + mSystemOSEntities = new loadSystemOSEntitiesAsyncTask(mSystemOSDao, mSystemOSEntities).execute().get(); + + // Set Data in LiveData + for (Map.Entry> entry : mSystemOSEntities.entrySet()) + { + List OSModels = mSystemOSModels.get(entry.getKey()); + if (OSModels == null) + { + OSModels = new LinkedList<>(); + } + for (SystemOSEntity systemOSEntity : entry.getValue()) + { + OperatingSystemEntity osEntity = mOSEntities.get(systemOSEntity.OSId); + OSModels.add(new OperatingSystemModel(osEntity.Name, systemOSEntity.ArchitectureName, systemOSEntity.Version)); + } + mSystemOSModels.put(entry.getKey(), OSModels); + } + mLiveDataSystemOSModels.postValue(mSystemOSModels); + } catch (InterruptedException e) { + e.printStackTrace(); + } catch (ExecutionException e) { + e.printStackTrace(); + } + } + + private void loadFromAPI(Context context, UUID systemId) + { + //TODO Load from API every user defined time. + VolleySingleton.getInstance(context).addToRequestQueue(new APISystemOperatingSystemRequest(context, systemId)); + } + + public LiveData> getSystemOSsLiveData(final UUID systemId, Context context) + { + loadFromAPI(context, systemId); + return Transformations.map(mLiveDataSystemOSModels, new Function>, List>() { + @Override + public List apply(HashMap> input) { + List osModelList = input.get(systemId); + if (osModelList == null) + { + osModelList = new LinkedList<>(); + } + if (osModelList.isEmpty()) + { + osModelList.add(new OperatingSystemModel()); + } + return osModelList; + } + }); + } + + public List getSystemOSs(UUID systemId) + { + return mSystemOSEntities.get(systemId); + } + + public OperatingSystemEntity getOS(UUID id) + { + return mOSEntities.get(id); + } + + public void upsert(UUID systemId, List systemOSEntities, List osEntities) + { + try { + new upsertOSAsyncTask(mOperatingSystemDao, systemId, osEntities).execute().get(); + new upsertSystemOSAsyncTask(mSystemOSDao, systemId, systemOSEntities).execute().get(); + } catch (InterruptedException e) { + e.printStackTrace(); + } catch (ExecutionException e) { + e.printStackTrace(); + } + List systemOSModels = new LinkedList<>(); + for (OperatingSystemEntity osEntity : osEntities) + { + mOSEntities.put(osEntity.Id, osEntity); + } + for (SystemOSEntity systemOSEntity : systemOSEntities) + { + OperatingSystemEntity osEntity = mOSEntities.get(systemOSEntity.OSId); + systemOSModels.add(new OperatingSystemModel(osEntity.Name, systemOSEntity.ArchitectureName, systemOSEntity.Version)); + } + mSystemOSEntities.put(systemId, systemOSEntities); + mSystemOSModels.put(systemId, systemOSModels); + mLiveDataSystemOSModels.postValue(mSystemOSModels); + } + + private static class loadOSEntitiesAsyncTask extends AsyncTask> { + + private OperatingSystemDao mOperatingSystemDao; + private HashMap mOSEntities; + + + loadOSEntitiesAsyncTask(OperatingSystemDao operatingSystemDao, HashMap osEntities) { + mOperatingSystemDao = operatingSystemDao; + mOSEntities = osEntities; + } + + protected HashMap doInBackground(final Void... voids) { + for (OperatingSystemEntity operatingSystemEntity : mOperatingSystemDao.get()) + { + mOSEntities.put(operatingSystemEntity.Id, operatingSystemEntity); + } + return mOSEntities; + } + + } + + private static class loadSystemOSEntitiesAsyncTask extends AsyncTask>> { + + private SystemOSDao mSystemOSDao; + private HashMap> mSystemOSEntities; + + + loadSystemOSEntitiesAsyncTask(SystemOSDao systemOSDao, HashMap> systemOSEntities) { + mSystemOSDao = systemOSDao; + mSystemOSEntities = systemOSEntities; + } + + protected HashMap> doInBackground(final Void... voids) { + for (SystemOSEntity systemOSEntity : mSystemOSDao.get()) + { + List OSEntities = mSystemOSEntities.get(systemOSEntity.SystemId); + if (OSEntities == null) + { + OSEntities = new LinkedList<>(); + } + OSEntities.add(systemOSEntity); + mSystemOSEntities.put(systemOSEntity.SystemId, OSEntities); + } + return mSystemOSEntities; + } + + } + + private static class upsertOSAsyncTask extends AsyncTask { + + private OperatingSystemDao mOperatingSystemDao; + private UUID mSystemId; + + private List mOperatingSystemEntities; + + upsertOSAsyncTask(OperatingSystemDao operatingSystemDao, UUID systemId, List operatingSystemEntities) { + mOperatingSystemDao = operatingSystemDao; + mSystemId = systemId; + mOperatingSystemEntities = operatingSystemEntities; + } + + protected Void doInBackground(final Void... voids) { + for (OperatingSystemEntity operatingSystemEntity: mOperatingSystemEntities) { + mOperatingSystemDao.upsert(operatingSystemEntity); + } + return null; + } + + } + + private static class upsertSystemOSAsyncTask extends AsyncTask { + + private SystemOSDao mSystemOSDao; + private UUID mSystemId; + + private List mSystemCPUEntities; + + upsertSystemOSAsyncTask(SystemOSDao systemOSDao, UUID systemId, List systemOSEntities) { + mSystemOSDao = systemOSDao; + mSystemId = systemId; + mSystemCPUEntities = systemOSEntities; + } + + protected Void doInBackground(final Void... voids) { + mSystemOSDao.deleteBySystemId(mSystemId); + for (SystemOSEntity systemOSEntity: mSystemCPUEntities) { + mSystemOSDao.insert(systemOSEntity); + } + return null; + } + + } + +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/repository/RAMRepository.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/repository/RAMRepository.java new file mode 100644 index 0000000..f154903 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/repository/RAMRepository.java @@ -0,0 +1,220 @@ +package uk.co.syski.client.android.model.repository; + +import android.arch.core.util.Function; +import android.arch.lifecycle.LiveData; +import android.arch.lifecycle.MutableLiveData; +import android.arch.lifecycle.Transformations; +import android.content.Context; +import android.os.AsyncTask; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ExecutionException; + +import uk.co.syski.client.android.model.api.VolleySingleton; +import uk.co.syski.client.android.model.api.requests.system.APISystemRAMRequest; +import uk.co.syski.client.android.model.database.SyskiCache; +import uk.co.syski.client.android.model.database.dao.RAMDao; +import uk.co.syski.client.android.model.database.dao.linking.SystemRAMDao; +import uk.co.syski.client.android.model.database.entity.RAMEntity; +import uk.co.syski.client.android.model.database.entity.linking.SystemRAMEntity; +import uk.co.syski.client.android.model.viewmodel.SystemRAMModel; + +public enum RAMRepository { + INSTANCE; + + // Database DAO's + private RAMDao mRAMDao; + private SystemRAMDao mSystemRAMDao; + + // RAM Cache in Memory + private HashMap mRAMEntities; + private HashMap> mSystemRAMEntities; + private HashMap> mSystemRAMModels; + + // LiveData + private MutableLiveData>> mLiveDataSystemRAMEntities; + + RAMRepository() { + mRAMDao = SyskiCache.GetDatabase().RAMDao(); + mSystemRAMDao = SyskiCache.GetDatabase().SystemRAMDao(); + + mRAMEntities = new HashMap<>(); + mSystemRAMEntities = new HashMap<>(); + mSystemRAMModels = new HashMap<>(); + + mLiveDataSystemRAMEntities = new MutableLiveData<>(); + loadFromDatabase(); + } + + private void loadFromDatabase() + { + try { + // Load data from Database for RAM's + mRAMEntities = new loadRAMEntitiesAsyncTask(mRAMDao, mRAMEntities).execute().get(); + + // Load data from Database for System RAM's + mSystemRAMEntities = new loadSystemRAMEntitiesAsyncTask(mSystemRAMDao, mSystemRAMEntities).execute().get(); + + // Set Data in LiveData + for (Map.Entry> entry : mSystemRAMEntities.entrySet()) + { + List RAMModels = mSystemRAMModels.get(entry.getKey()); + if (RAMModels == null) + { + RAMModels = new LinkedList<>(); + } + for (SystemRAMEntity systemRAMEntity : entry.getValue()) + { + RAMEntity ramEntity = mRAMEntities.get(systemRAMEntity.RAMId); + RAMModels.add(new SystemRAMModel(ramEntity.ModelName, ramEntity.ManufacturerName, ramEntity.MemoryTypeName, ramEntity.MemoryBytes)); + } + mSystemRAMModels.put(entry.getKey(), RAMModels); + } + mLiveDataSystemRAMEntities.postValue(mSystemRAMModels); + } catch (InterruptedException | ExecutionException e) { + e.printStackTrace(); + } + } + + private void loadFromAPI(final Context context, final UUID systemId) + { + //TODO Load from API every user defined time. + VolleySingleton.getInstance(context).addToRequestQueue(new APISystemRAMRequest(context, systemId)); + } + + public LiveData> getSystemRAMsLiveData(final UUID systemId, Context context) + { + loadFromAPI(context, systemId); + return Transformations.map(mLiveDataSystemRAMEntities, new Function>, List>() { + @Override + public List apply(HashMap> input) { + List ramModelList = input.get(systemId); + if (ramModelList == null) + { + ramModelList = new LinkedList<>(); + } + if (ramModelList.isEmpty()) + { + ramModelList.add(new SystemRAMModel()); + } + return ramModelList; + } + }); + } + + public RAMEntity get(UUID id) + { + return mRAMEntities.get(id); + } + + public void upsert(UUID systemId, List systemRAMEntities, List ramEntities) + { + try { + new upsertRAMAsyncTask(mRAMDao, ramEntities).execute().get(); + new upsertSystemRAMAsyncTask(mSystemRAMDao, systemId, systemRAMEntities).execute().get(); + } catch (InterruptedException | ExecutionException e) { + e.printStackTrace(); + } + List systemRAMModels = new LinkedList<>(); + for (RAMEntity ramEntity : ramEntities) + { + mRAMEntities.put(ramEntity.Id, ramEntity); + systemRAMModels.add(new SystemRAMModel(ramEntity.ModelName, ramEntity.ManufacturerName, ramEntity.MemoryTypeName, ramEntity.MemoryBytes)); + } + mSystemRAMModels.put(systemId, systemRAMModels); + mLiveDataSystemRAMEntities.postValue(mSystemRAMModels); + } + + private static class loadRAMEntitiesAsyncTask extends AsyncTask> { + + private RAMDao mRAMDao; + private HashMap mRAMEntities; + + + loadRAMEntitiesAsyncTask(RAMDao ramDao, HashMap RAMEntities) { + mRAMDao = ramDao; + mRAMEntities = RAMEntities; + } + + protected HashMap doInBackground(final Void... voids) { + for (RAMEntity ramEntity : mRAMDao.get()) + { + mRAMEntities.put(ramEntity.Id, ramEntity); + } + return mRAMEntities; + } + + } + + private static class loadSystemRAMEntitiesAsyncTask extends AsyncTask>> { + + private SystemRAMDao mSystemRAMDao; + private HashMap> mSystemRAMEntities; + + loadSystemRAMEntitiesAsyncTask(SystemRAMDao systemRAMDao, HashMap> systemRAMEntities) { + mSystemRAMDao = systemRAMDao; + mSystemRAMEntities = systemRAMEntities; + } + + protected HashMap> doInBackground(final Void... voids) { + for (SystemRAMEntity SystemRAMEntity : mSystemRAMDao.get()) + { + List RAMEntities = mSystemRAMEntities.get(SystemRAMEntity.SystemId); + if (RAMEntities == null) + { + RAMEntities = new LinkedList<>(); + } + RAMEntities.add(SystemRAMEntity); + mSystemRAMEntities.put(SystemRAMEntity.SystemId, RAMEntities); + } + return mSystemRAMEntities; + } + + } + + private static class upsertRAMAsyncTask extends AsyncTask { + + private RAMDao mRAMDao; + private List mRAMEntities; + + upsertRAMAsyncTask(RAMDao ramDao, List ramEntities) { + mRAMDao = ramDao; + mRAMEntities = ramEntities; + } + + protected Void doInBackground(final Void... voids) { + for (RAMEntity ramEntity: mRAMEntities) { + mRAMDao.upsert(ramEntity); + } + return null; + } + + } + + private static class upsertSystemRAMAsyncTask extends AsyncTask { + + private SystemRAMDao mSystemRAMDao; + private UUID mSystemId; + private List mSystemRAMEntities; + + upsertSystemRAMAsyncTask(SystemRAMDao systemRAMDao, UUID systemId, List systemRAMEntities) { + mSystemRAMDao = systemRAMDao; + mSystemId = systemId; + mSystemRAMEntities = systemRAMEntities; + } + + protected Void doInBackground(final Void... voids) { + mSystemRAMDao.deleteBySystemId(mSystemId); + for (SystemRAMEntity systemRAMEntity: mSystemRAMEntities) { + mSystemRAMDao.insert(systemRAMEntity); + } + return null; + } + + } + +} \ No newline at end of file diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/repository/Repository.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/repository/Repository.java new file mode 100644 index 0000000..6f6ef0c --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/repository/Repository.java @@ -0,0 +1,57 @@ +package uk.co.syski.client.android.model.repository; + +public enum Repository { + INSTANCE; + + public static Repository getInstance() { + return INSTANCE; + } + + private UserRepository mUserRepository = new UserRepository(); + + public synchronized UserRepository getUserRepository() + { + return mUserRepository; + } + + public synchronized SystemRepository getSystemRepository() + { + return SystemRepository.INSTANCE; + } + + public synchronized CPURepository getCPURepository() + { + return CPURepository.INSTANCE; + } + + public synchronized GPURepository getGPURepository() + { + return GPURepository.INSTANCE; + } + + public synchronized RAMRepository getRAMRepository() + { + return RAMRepository.INSTANCE; + } + + public synchronized StorageRepository getStorageRepository() + { + return StorageRepository.INSTANCE; + } + + public synchronized MOBORepository getMOBORepository() + { + return MOBORepository.INSTANCE; + } + + public synchronized BIOSRepository getBIOSRepository() + { + return BIOSRepository.INSTANCE; + } + + public synchronized OSRepository getOSRepository() + { + return OSRepository.INSTANCE; + } + +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/repository/StorageRepository.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/repository/StorageRepository.java new file mode 100644 index 0000000..f4bfc93 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/repository/StorageRepository.java @@ -0,0 +1,220 @@ +package uk.co.syski.client.android.model.repository; + +import android.arch.core.util.Function; +import android.arch.lifecycle.LiveData; +import android.arch.lifecycle.MutableLiveData; +import android.arch.lifecycle.Transformations; +import android.content.Context; +import android.os.AsyncTask; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ExecutionException; + +import uk.co.syski.client.android.model.api.VolleySingleton; +import uk.co.syski.client.android.model.api.requests.system.APISystemStorageRequest; +import uk.co.syski.client.android.model.database.SyskiCache; +import uk.co.syski.client.android.model.database.dao.StorageDao; +import uk.co.syski.client.android.model.database.dao.linking.SystemStorageDao; +import uk.co.syski.client.android.model.database.entity.StorageEntity; +import uk.co.syski.client.android.model.database.entity.linking.SystemStorageEntity; +import uk.co.syski.client.android.model.viewmodel.SystemStorageModel; + +public enum StorageRepository { + INSTANCE; + + // Database DAO's + private StorageDao mStorageDao; + private SystemStorageDao mSystemStorageDao; + + // Storage Cache in Memory + private HashMap mStorageEntities; + private HashMap> mSystemStorageEntities; + private HashMap> mSystemStorageModels; + + // LiveData + private MutableLiveData>> mLiveDataSystemStorageEntities; + + StorageRepository() { + mStorageDao = SyskiCache.GetDatabase().StorageDao(); + mSystemStorageDao = SyskiCache.GetDatabase().SystemStorageDao(); + + mStorageEntities = new HashMap<>(); + mSystemStorageEntities = new HashMap<>(); + mSystemStorageModels = new HashMap<>(); + + mLiveDataSystemStorageEntities = new MutableLiveData<>(); + loadFromDatabase(); + } + + private void loadFromDatabase() + { + try { + // Load data from Database for Storage's + mStorageEntities = new loadStorageEntitiesAsyncTask(mStorageDao, mStorageEntities).execute().get(); + + // Load data from Database for System Storage's + mSystemStorageEntities = new loadSystemStorageEntitiesAsyncTask(mSystemStorageDao, mSystemStorageEntities).execute().get(); + + // Set Data in LiveData + for (Map.Entry> entry : mSystemStorageEntities.entrySet()) + { + List StorageModels = mSystemStorageModels.get(entry.getKey()); + if (StorageModels == null) + { + StorageModels = new LinkedList<>(); + } + for (SystemStorageEntity systemStorageEntity : entry.getValue()) + { + StorageEntity storageEntity = mStorageEntities.get(systemStorageEntity.StorageId); + StorageModels.add(new SystemStorageModel(storageEntity.ModelName, storageEntity.ManufacturerName, storageEntity.MemoryTypeName, storageEntity.MemoryBytes)); + } + mSystemStorageModels.put(entry.getKey(), StorageModels); + } + mLiveDataSystemStorageEntities.postValue(mSystemStorageModels); + } catch (InterruptedException | ExecutionException e) { + e.printStackTrace(); + } + } + + private void loadFromAPI(final Context context, final UUID systemId) + { + //TODO Load from API every user defined time. + VolleySingleton.getInstance(context).addToRequestQueue(new APISystemStorageRequest(context, systemId)); + } + + public LiveData> getSystemStoragesLiveData(final UUID systemId, Context context) + { + loadFromAPI(context, systemId); + return Transformations.map(mLiveDataSystemStorageEntities, new Function>, List>() { + @Override + public List apply(HashMap> input) { + List storageModelList = input.get(systemId); + if (storageModelList == null) + { + storageModelList = new LinkedList<>(); + } + if (storageModelList.isEmpty()) + { + storageModelList.add(new SystemStorageModel()); + } + return storageModelList; + } + }); + } + + public StorageEntity get(UUID id) + { + return mStorageEntities.get(id); + } + + public void upsert(UUID systemId, List systemStorageEntities, List storageEntities) + { + try { + new upsertStorageAsyncTask(mStorageDao, storageEntities).execute().get(); + new upsertSystemStorageAsyncTask(mSystemStorageDao, systemId, systemStorageEntities).execute().get(); + } catch (InterruptedException | ExecutionException e) { + e.printStackTrace(); + } + List systemStorageModels = new LinkedList<>(); + for (StorageEntity storageEntity : storageEntities) + { + mStorageEntities.put(storageEntity.Id, storageEntity); + systemStorageModels.add(new SystemStorageModel(storageEntity.ModelName, storageEntity.ManufacturerName, storageEntity.MemoryTypeName, storageEntity.MemoryBytes)); + } + mSystemStorageModels.put(systemId, systemStorageModels); + mLiveDataSystemStorageEntities.postValue(mSystemStorageModels); + } + + private static class loadStorageEntitiesAsyncTask extends AsyncTask> { + + private StorageDao mStorageDao; + private HashMap mStorageEntities; + + + loadStorageEntitiesAsyncTask(StorageDao storageDao, HashMap StorageEntities) { + mStorageDao = storageDao; + mStorageEntities = StorageEntities; + } + + protected HashMap doInBackground(final Void... voids) { + for (StorageEntity storageEntity : mStorageDao.get()) + { + mStorageEntities.put(storageEntity.Id, storageEntity); + } + return mStorageEntities; + } + + } + + private static class loadSystemStorageEntitiesAsyncTask extends AsyncTask>> { + + private SystemStorageDao mSystemStorageDao; + private HashMap> mSystemStorageEntities; + + loadSystemStorageEntitiesAsyncTask(SystemStorageDao systemStorageDao, HashMap> systemStorageEntities) { + mSystemStorageDao = systemStorageDao; + mSystemStorageEntities = systemStorageEntities; + } + + protected HashMap> doInBackground(final Void... voids) { + for (SystemStorageEntity SystemStorageEntity : mSystemStorageDao.get()) + { + List StorageEntities = mSystemStorageEntities.get(SystemStorageEntity.SystemId); + if (StorageEntities == null) + { + StorageEntities = new LinkedList<>(); + } + StorageEntities.add(SystemStorageEntity); + mSystemStorageEntities.put(SystemStorageEntity.SystemId, StorageEntities); + } + return mSystemStorageEntities; + } + + } + + private static class upsertStorageAsyncTask extends AsyncTask { + + private StorageDao mStorageDao; + private List mStorageEntities; + + upsertStorageAsyncTask(StorageDao storageDao, List storageEntities) { + mStorageDao = storageDao; + mStorageEntities = storageEntities; + } + + protected Void doInBackground(final Void... voids) { + for (StorageEntity storageEntity: mStorageEntities) { + mStorageDao.upsert(storageEntity); + } + return null; + } + + } + + private static class upsertSystemStorageAsyncTask extends AsyncTask { + + private SystemStorageDao mSystemStorageDao; + private UUID mSystemId; + private List mSystemStorageEntities; + + upsertSystemStorageAsyncTask(SystemStorageDao systemStorageDao, UUID systemId, List systemStorageEntities) { + mSystemStorageDao = systemStorageDao; + mSystemId = systemId; + mSystemStorageEntities = systemStorageEntities; + } + + protected Void doInBackground(final Void... voids) { + mSystemStorageDao.deleteBySystemId(mSystemId); + for (SystemStorageEntity systemStorageEntity: mSystemStorageEntities) { + mSystemStorageDao.insert(systemStorageEntity); + } + return null; + } + + } + +} \ No newline at end of file diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/repository/SystemCPUDataRepository.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/repository/SystemCPUDataRepository.java new file mode 100644 index 0000000..abcd221 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/repository/SystemCPUDataRepository.java @@ -0,0 +1,93 @@ +package uk.co.syski.client.android.model.repository; + +import android.app.Application; +import android.arch.lifecycle.MutableLiveData; +import android.os.Handler; + +import com.android.volley.Response; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.List; +import java.util.Timer; +import java.util.TimerTask; +import java.util.UUID; + +import uk.co.syski.client.android.model.api.VolleySingleton; +import uk.co.syski.client.android.model.api.requests.system.APISystemCPUDataRequest; +import uk.co.syski.client.android.model.database.entity.data.CPUDataEntity; + +public class SystemCPUDataRepository { + + private Timer mTimer; + private TimerTask mTimerTask; + private Handler mTimerHandler = new Handler(); + private SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSSSSS"); + + private Application mApplication; + private final MutableLiveData> mCPUDataEntity; + private UUID mActiveSystemId; + + public SystemCPUDataRepository(Application application, UUID systemId) + { + mCPUDataEntity = new MutableLiveData(); + mActiveSystemId = systemId; + mApplication = application; + } + + public void stop() + { + if(mTimer != null){ + mTimer.cancel(); + mTimer.purge(); + } + } + + public void start(){ + mTimer = new Timer(); + mTimerTask = new TimerTask() { + public void run() { + mTimerHandler.post(new Runnable() { + public void run(){ + VolleySingleton.getInstance(mApplication).addToRequestQueue(new APISystemCPUDataRequest(mApplication, mActiveSystemId, + new Response.Listener() { + @Override + public void onResponse(JSONArray response) { + List cpuDataEntities = new ArrayList(); + for (int i = 0; i < response.length(); i++) + { + CPUDataEntity cpuDataEntity = new CPUDataEntity(); + cpuDataEntity.SystemId = mActiveSystemId; + try { + cpuDataEntity.Load = Float.parseFloat(((JSONObject) response.get(i)).getString("load")); + cpuDataEntity.Processes = Float.parseFloat(((JSONObject) response.get(i)).getString("processes")); + cpuDataEntity.CollectionDateTime = dateFormat.parse(((JSONObject) response.get(i)).getString("collectionDateTime")); + } catch (JSONException e) { + e.printStackTrace(); + } catch (ParseException e) { + e.printStackTrace(); + } + cpuDataEntities.add(cpuDataEntity); + } + mCPUDataEntity.postValue(cpuDataEntities); + } + }, null)); + } + }); + } + }; + + mTimer.schedule(mTimerTask, 1, 3000); + } + + public MutableLiveData> get() + { + return mCPUDataEntity; + } + +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/repository/SystemProcessesRepository.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/repository/SystemProcessesRepository.java new file mode 100644 index 0000000..22dd9e0 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/repository/SystemProcessesRepository.java @@ -0,0 +1,99 @@ +package uk.co.syski.client.android.model.repository; + +import android.app.Application; +import android.arch.lifecycle.MutableLiveData; +import android.os.Handler; + +import com.android.volley.Response; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.List; +import java.util.Timer; +import java.util.TimerTask; +import java.util.UUID; + +import uk.co.syski.client.android.model.api.VolleySingleton; +import uk.co.syski.client.android.model.api.requests.system.APISystemProcessDataRequest; +import uk.co.syski.client.android.model.database.entity.data.SystemProcessesEntity; + +public class SystemProcessesRepository { + + private Timer mTimer; + private TimerTask mTimerTask; + private Handler mTimerHandler = new Handler(); + private SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSSSSS"); + + private Application mApplication; + private final MutableLiveData> mProcessesDataEntity; + private UUID mActiveSystemId; + + public SystemProcessesRepository(Application application, UUID systemId) + { + mProcessesDataEntity = new MutableLiveData(); + mActiveSystemId = systemId; + mApplication = application; + } + + public void stop() + { + if(mTimer != null){ + mTimer.cancel(); + mTimer.purge(); + } + } + + public void start(){ + mTimer = new Timer(); + mTimerTask = new TimerTask() { + public void run() { + mTimerHandler.post(new Runnable() { + public void run(){ + VolleySingleton.getInstance(mApplication).addToRequestQueue(new APISystemProcessDataRequest(mApplication, mActiveSystemId, + new Response.Listener() { + @Override + public void onResponse(JSONArray response) { + List processesDataEntities = new ArrayList(); + for (int i = 0; i < response.length(); i++) + { + SystemProcessesEntity systemProcessesEntity = new SystemProcessesEntity(); + systemProcessesEntity.SystemId = mActiveSystemId; + try { + systemProcessesEntity.Id = Integer.parseInt(((JSONObject) response.get(i)).getString("id")); + systemProcessesEntity.KernelTime = Long.parseLong(((JSONObject) response.get(i)).getString("kernelTime")); + systemProcessesEntity.MemSize = Long.parseLong((((JSONObject) response.get(i)).getString("memSize"))); + systemProcessesEntity.Name = ((JSONObject) response.get(i)).getString("name"); + systemProcessesEntity.ParentId = Integer.parseInt(((JSONObject) response.get(i)).getString("parentId")); + systemProcessesEntity.Path = ((JSONObject) response.get(i)).getString("path"); + systemProcessesEntity.Threads = Integer.parseInt(((JSONObject) response.get(i)).getString("threads")); + systemProcessesEntity.UpTime = Long.parseLong(((JSONObject) response.get(i)).getString("upTime")); + systemProcessesEntity.CollectionDateTime = dateFormat.parse(((JSONObject) response.get(i)).getString("collectionDateTime")); + } catch (JSONException e) { + e.printStackTrace(); + } catch (ParseException e) { + e.printStackTrace(); + } + processesDataEntities.add(systemProcessesEntity); + } + mProcessesDataEntity.postValue(processesDataEntities); + } + }, null)); + } + }); + } + }; + + mTimer.schedule(mTimerTask, 1, 30000); + } + + public MutableLiveData> get() + { + return mProcessesDataEntity; + } + +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/repository/SystemRAMDataRepository.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/repository/SystemRAMDataRepository.java new file mode 100644 index 0000000..db9db31 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/repository/SystemRAMDataRepository.java @@ -0,0 +1,92 @@ +package uk.co.syski.client.android.model.repository; + +import android.app.Application; +import android.arch.lifecycle.MutableLiveData; +import android.os.Handler; + +import com.android.volley.Response; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.List; +import java.util.Timer; +import java.util.TimerTask; +import java.util.UUID; + +import uk.co.syski.client.android.model.api.VolleySingleton; +import uk.co.syski.client.android.model.api.requests.system.APISystemRAMDataRequest; +import uk.co.syski.client.android.model.database.entity.data.RAMDataEntity; + +public class SystemRAMDataRepository { + + private Timer mTimer; + private TimerTask mTimerTask; + private Handler mTimerHandler = new Handler(); + private SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSSSSS"); + + private Application mApplication; + private final MutableLiveData> mRAMDataEntity; + private UUID mActiveSystemId; + + public SystemRAMDataRepository(Application application, UUID systemId) + { + mRAMDataEntity = new MutableLiveData(); + mActiveSystemId = systemId; + mApplication = application; + } + + public void stop() + { + if(mTimer != null){ + mTimer.cancel(); + mTimer.purge(); + } + } + + public void start(){ + mTimer = new Timer(); + mTimerTask = new TimerTask() { + public void run() { + mTimerHandler.post(new Runnable() { + public void run(){ + VolleySingleton.getInstance(mApplication).addToRequestQueue(new APISystemRAMDataRequest(mApplication, mActiveSystemId, + new Response.Listener() { + @Override + public void onResponse(JSONArray response) { + List ramDataEntities = new ArrayList(); + for (int i = 0; i < response.length(); i++) + { + RAMDataEntity ramDataEntity = new RAMDataEntity(); + ramDataEntity.SystemId = mActiveSystemId; + try { + ramDataEntity.Free = Integer.parseInt(((JSONObject) response.get(i)).getString("free")); + ramDataEntity.CollectionDateTime = dateFormat.parse(((JSONObject) response.get(i)).getString("collectionDateTime")); + } catch (JSONException e) { + e.printStackTrace(); + } catch (ParseException e) { + e.printStackTrace(); + } + ramDataEntities.add(ramDataEntity); + } + mRAMDataEntity.postValue(ramDataEntities); + } + }, null)); + } + }); + } + }; + + mTimer.schedule(mTimerTask, 1, 3000); + } + + public MutableLiveData> get() + { + return mRAMDataEntity; + } + +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/repository/SystemRepository.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/repository/SystemRepository.java new file mode 100644 index 0000000..7cfd3a9 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/repository/SystemRepository.java @@ -0,0 +1,260 @@ +package uk.co.syski.client.android.model.repository; + +import android.app.Application; +import android.arch.core.util.Function; +import android.arch.lifecycle.LiveData; +import android.arch.lifecycle.MutableLiveData; +import android.arch.lifecycle.Transformations; +import android.content.Context; +import android.os.AsyncTask; +import android.os.Handler; + +import com.android.volley.Response; +import com.android.volley.VolleyError; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.text.ParseException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Timer; +import java.util.TimerTask; +import java.util.UUID; +import java.util.concurrent.ExecutionException; + +import uk.co.syski.client.android.model.api.VolleySingleton; +import uk.co.syski.client.android.model.api.requests.system.APISystemCPUDataRequest; +import uk.co.syski.client.android.model.api.requests.system.APISystemPingRequest; +import uk.co.syski.client.android.model.api.requests.system.APISystemRemove; +import uk.co.syski.client.android.model.api.requests.system.APISystemsRequest; +import uk.co.syski.client.android.model.database.SyskiCache; +import uk.co.syski.client.android.model.database.dao.SystemDao; +import uk.co.syski.client.android.model.database.entity.StorageEntity; +import uk.co.syski.client.android.model.database.entity.SystemEntity; +import uk.co.syski.client.android.model.database.entity.data.CPUDataEntity; +import uk.co.syski.client.android.model.database.entity.linking.SystemStorageEntity; +import uk.co.syski.client.android.model.viewmodel.SystemModel; +import uk.co.syski.client.android.model.viewmodel.SystemStorageModel; + +public enum SystemRepository { + INSTANCE; + + private Timer mTimer; + private TimerTask mTimerTask; + private Handler mTimerHandler = new Handler(); + + // Database DAO's + private SystemDao mSystemDao; + + // Systems in Memory + private HashMap mSystemEntities; + private HashMap mSystemModels; + + // System in LiveData + private MutableLiveData> mLiveDataSystemEntities; + + SystemRepository() { + mSystemDao = SyskiCache.GetDatabase().SystemDao(); + + mSystemEntities = new HashMap<>(); + mSystemModels = new HashMap<>(); + + mLiveDataSystemEntities = new MutableLiveData<>(); + loadFromDatabase(); + } + + public void start(final Application application){ + mTimer = new Timer(); + mTimerTask = new TimerTask() { + public void run() { + mTimerHandler.post(new Runnable() { + public void run(){ + VolleySingleton.getInstance(application.getBaseContext()).addToRequestQueue(new APISystemsRequest(application.getBaseContext())); + for (final Map.Entry entry: mSystemModels.entrySet()) { + VolleySingleton.getInstance(application).addToRequestQueue(new APISystemPingRequest(application, entry.getKey(), + new Response.Listener() { + @Override + public void onResponse(JSONObject response) { + SystemModel systemModel = entry.getValue(); + try { + systemModel.setOnline(); + systemModel.setPing(Float.parseFloat(response.getString("ping"))); + } catch (JSONException e) { + e.printStackTrace(); + } + mLiveDataSystemEntities.postValue(mSystemModels); + } + }, + new Response.ErrorListener() { + @Override + public void onErrorResponse(VolleyError error) { + SystemModel systemModel = entry.getValue(); + systemModel.setOffline(); + mLiveDataSystemEntities.postValue(mSystemModels); + } + } + ) + ); + } + } + }); + } + }; + mTimer.schedule(mTimerTask, 1, 10000); + } + + public void stop() + { + if(mTimer != null){ + mTimer.cancel(); + mTimer.purge(); + } + } + + private void loadFromDatabase() + { + try { + // Load data from Database for CPU's + mSystemEntities = new loadSystemEntitiesAsyncTask(mSystemDao, mSystemEntities).execute().get(); + + // Set Data in LiveData + for (Map.Entry entry : mSystemEntities.entrySet()) + { + SystemModel systemModel = mSystemModels.get(entry.getKey()); + if (systemModel == null) + { + SystemEntity systemEntity = entry.getValue(); + systemModel = new SystemModel(systemEntity.Id, systemEntity.HostName, systemEntity.ModelName, systemEntity.ManufacturerName); + } + mSystemModels.put(entry.getKey(), systemModel); + } + mLiveDataSystemEntities.postValue(mSystemModels); + } catch (InterruptedException e) { + e.printStackTrace(); + } catch (ExecutionException e) { + e.printStackTrace(); + } + } + + private void loadFromAPI(Context context) + { + //TODO Load from API every user defined time. + VolleySingleton.getInstance(context).addToRequestQueue(new APISystemsRequest(context)); + } + + public LiveData> getSystemsLiveData(Context context) + { + loadFromAPI(context); + return Transformations.map(mLiveDataSystemEntities, new Function, List>() { + @Override + public List apply(HashMap input) { + return new LinkedList<>(input.values()); + } + }); + } + + public LiveData getSystemLiveData(final UUID systemId, Context context) + { + loadFromAPI(context); + return Transformations.map(mLiveDataSystemEntities, new Function, SystemModel>() { + @Override + public SystemModel apply(HashMap input) { + return input.get(systemId); + } + }); + } + + public SystemEntity get(UUID id) + { + return mSystemEntities.get(id); + } + + public void upsert(SystemEntity systemEntity) + { + try { + new upsertSystemsAsyncTask(mSystemDao).execute(systemEntity).get(); + } catch (InterruptedException e) { + e.printStackTrace(); + } catch (ExecutionException e) { + e.printStackTrace(); + } + mSystemEntities.put(systemEntity.Id, systemEntity); + if (mSystemModels.get(systemEntity.Id) == null) + { + mSystemModels.put(systemEntity.Id, new SystemModel(systemEntity.Id, systemEntity.HostName, systemEntity.ModelName, systemEntity.ManufacturerName)); + mLiveDataSystemEntities.postValue(mSystemModels); + } + } + + public void delete(UUID id, Context context) + { + try { + new deleteSystemsAsyncTask(mSystemDao).execute(mSystemEntities.get(id)).get(); + } catch (InterruptedException e) { + e.printStackTrace(); + } catch (ExecutionException e) { + e.printStackTrace(); + } + mSystemEntities.remove(id); + mSystemModels.remove(id); + VolleySingleton.getInstance(context).addToRequestQueue(new APISystemRemove(context, id)); + mLiveDataSystemEntities.postValue(mSystemModels); + } + + private static class loadSystemEntitiesAsyncTask extends AsyncTask> { + + private SystemDao mSystemDao; + private HashMap mSystemEntities; + + + loadSystemEntitiesAsyncTask(SystemDao systemDao, HashMap SystemEntities) { + mSystemDao = systemDao; + mSystemEntities = SystemEntities; + } + + protected HashMap doInBackground(final Void... voids) { + for (SystemEntity systemEntity : mSystemDao.get()) + { + mSystemEntities.put(systemEntity.Id, systemEntity); + } + return mSystemEntities; + } + + } + + private static class upsertSystemsAsyncTask extends AsyncTask { + + private SystemDao mSystemDao; + + upsertSystemsAsyncTask(SystemDao systemDao) { + mSystemDao = systemDao; + } + + protected Void doInBackground(final SystemEntity... systemEntities) { + for (SystemEntity systemEntity: systemEntities) { + mSystemDao.upsert(systemEntity); + } + return null; + } + + } + + private static class deleteSystemsAsyncTask extends AsyncTask { + private SystemDao mSystemDao; + + deleteSystemsAsyncTask(SystemDao systemDao) { mSystemDao = systemDao;} + + protected Void doInBackground(final SystemEntity... systemEntities) { + for (SystemEntity systemEntity: systemEntities) { + mSystemDao.delete(systemEntity); + } + return null; + } + } + +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/repository/SystemStorageDataRepository.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/repository/SystemStorageDataRepository.java new file mode 100644 index 0000000..77c1c54 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/repository/SystemStorageDataRepository.java @@ -0,0 +1,97 @@ +package uk.co.syski.client.android.model.repository; + +import android.app.Application; +import android.arch.lifecycle.MutableLiveData; +import android.os.Handler; + +import com.android.volley.Response; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.List; +import java.util.Timer; +import java.util.TimerTask; +import java.util.UUID; + +import uk.co.syski.client.android.model.api.VolleySingleton; +import uk.co.syski.client.android.model.api.requests.system.APISystemStorageDataRequest; +import uk.co.syski.client.android.model.database.entity.data.StorageDataEntity; + +public class SystemStorageDataRepository { + + private Timer mTimer; + private TimerTask mTimerTask; + private Handler mTimerHandler = new Handler(); + private SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSSSSS"); + + private Application mApplication; + private final MutableLiveData> mStorageDataEntity; + private UUID mActiveSystemId; + + public SystemStorageDataRepository(Application application, UUID systemId) + { + mStorageDataEntity = new MutableLiveData(); + mActiveSystemId = systemId; + mApplication = application; + } + + public void stop() + { + if(mTimer != null){ + mTimer.cancel(); + mTimer.purge(); + } + } + + public void start(){ + mTimer = new Timer(); + mTimerTask = new TimerTask() { + public void run() { + mTimerHandler.post(new Runnable() { + public void run(){ + VolleySingleton.getInstance(mApplication).addToRequestQueue(new APISystemStorageDataRequest(mApplication, mActiveSystemId, + new Response.Listener() { + @Override + public void onResponse(JSONArray response) { + List storageDataEntities = new ArrayList(); + for (int i = 0; i < response.length(); i++) + { + StorageDataEntity storageDataEntity = new StorageDataEntity(); + storageDataEntity.SystemId = mActiveSystemId; + try { + storageDataEntity.Time = Float.parseFloat(((JSONObject) response.get(i)).getString("time")); + storageDataEntity.Transfers = Float.parseFloat(((JSONObject) response.get(i)).getString("transfers")); + storageDataEntity.Reads = Float.parseFloat(((JSONObject) response.get(i)).getString("reads")); + storageDataEntity.Writes = Float.parseFloat(((JSONObject) response.get(i)).getString("writes")); + storageDataEntity.ByteReads = Float.parseFloat(((JSONObject) response.get(i)).getString("byteReads")); + storageDataEntity.ByteWrites = Float.parseFloat(((JSONObject) response.get(i)).getString("byteWrites")); + storageDataEntity.CollectionDateTime = dateFormat.parse(((JSONObject) response.get(i)).getString("collectionDateTime")); + } catch (JSONException e) { + e.printStackTrace(); + } catch (ParseException e) { + e.printStackTrace(); + } + storageDataEntities.add(storageDataEntity); + } + mStorageDataEntity.postValue(storageDataEntities); + } + }, null)); + } + }); + } + }; + + mTimer.schedule(mTimerTask, 1, 3000); + } + + public MutableLiveData> get() + { + return mStorageDataEntity; + } + +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/repository/UserRepository.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/repository/UserRepository.java new file mode 100644 index 0000000..a949126 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/repository/UserRepository.java @@ -0,0 +1,159 @@ +package uk.co.syski.client.android.model.repository; + +import android.arch.lifecycle.MutableLiveData; +import android.os.AsyncTask; + +import java.util.List; +import java.util.UUID; +import java.util.concurrent.ExecutionException; + +import uk.co.syski.client.android.model.database.SyskiCache; +import uk.co.syski.client.android.model.database.dao.UserDao; +import uk.co.syski.client.android.model.database.entity.UserEntity; + +public class UserRepository { + + private UserDao mUserDao; + private MutableLiveData> mUserEntities; + private boolean mDataUpdated; + private UUID mActiveUserId; + private MutableLiveData mUserEntity; + + public UserRepository() { + mUserDao = SyskiCache.GetDatabase().UserDao(); + mUserEntities = new MutableLiveData(); + mUserEntity = new MutableLiveData(); + try { + mUserEntities.postValue(new getAsyncTask(mUserDao).execute().get()); + } catch (InterruptedException e) { + e.printStackTrace(); + } catch (ExecutionException e) { + e.printStackTrace(); + } + } + + public MutableLiveData> get() { + return mUserEntities; + } + + public MutableLiveData get(UUID userId) { + if (mDataUpdated || mActiveUserId == null || !mActiveUserId.equals(userId)) { + mActiveUserId = userId; + updateUserData(); + mDataUpdated = false; + } + return mUserEntity; + } + + public UserEntity getUser() { + if (mDataUpdated) { + updateUserData(); + mDataUpdated = false; + } + return mUserEntity.getValue(); + } + + public void setActiveUserId(UUID userId) + { + mActiveUserId = userId; + updateUserData(); + } + + public void insert(UserEntity userEntity) { + new insertAsyncTask(mUserDao).execute(userEntity); + updateData(); + } + + public void update(UserEntity userEntity) { + new updateAsyncTask(mUserDao).execute(userEntity); + updateData(); + if (userEntity.Id.equals(mActiveUserId)) + { + updateUserData(); + } + } + + public void updateUserData() { + try { + mUserEntity.postValue(new getUserAsyncTask(mUserDao).execute(mActiveUserId).get()); + mDataUpdated = true; + } catch (InterruptedException e) { + e.printStackTrace(); + } catch (ExecutionException e) { + e.printStackTrace(); + } + } + + public void updateData() { + try { + mUserEntities.postValue(new getAsyncTask(mUserDao).execute().get()); + mDataUpdated = true; + } catch (InterruptedException e) { + e.printStackTrace(); + } catch (ExecutionException e) { + e.printStackTrace(); + } + } + + + private static class getUserAsyncTask extends AsyncTask { + + private UserDao mAsyncTaskDao; + + getUserAsyncTask(UserDao dao) { + mAsyncTaskDao = dao; + } + + protected UserEntity doInBackground(final UUID... uuids) { + return mAsyncTaskDao.get(uuids[0]); + } + + } + + private static class getAsyncTask extends AsyncTask> { + + private UserDao mAsyncTaskDao; + + getAsyncTask(UserDao dao) { + mAsyncTaskDao = dao; + } + + protected List doInBackground(final Void... voids) { + return mAsyncTaskDao.get(); + } + + } + + private static class insertAsyncTask extends AsyncTask { + + private UserDao mAsyncTaskDao; + + insertAsyncTask(UserDao dao) { + mAsyncTaskDao = dao; + } + + @Override + protected Void doInBackground(final UserEntity... userEntities) { + mAsyncTaskDao.insert(userEntities); + return null; + } + + } + + private static class updateAsyncTask extends AsyncTask { + + private UserDao mAsyncTaskDao; + + updateAsyncTask(UserDao dao) { + mAsyncTaskDao = dao; + } + + @Override + protected Void doInBackground(final UserEntity... userEntities) { + mAsyncTaskDao.update(userEntities); + return null; + } + + } + +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/repository/package-info.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/repository/package-info.java new file mode 100644 index 0000000..aa4ab2d --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/repository/package-info.java @@ -0,0 +1,4 @@ +/** + * Contains all the data repositories, used to populate views using data from the Cache Database + */ +package uk.co.syski.client.android.model.repository; \ No newline at end of file diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/viewmodel/ModelUtil.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/viewmodel/ModelUtil.java new file mode 100644 index 0000000..46e4ad8 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/viewmodel/ModelUtil.java @@ -0,0 +1,10 @@ +package uk.co.syski.client.android.model.viewmodel; + +public class ModelUtil { + + public static String nullToUnknown(String value) + { + return (value == null || value.equalsIgnoreCase("null") ? "Unknown" : value); + } + +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/viewmodel/OperatingSystemModel.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/viewmodel/OperatingSystemModel.java new file mode 100644 index 0000000..81793df --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/viewmodel/OperatingSystemModel.java @@ -0,0 +1,37 @@ +package uk.co.syski.client.android.model.viewmodel; + +public class OperatingSystemModel { + + private String name; + private String architectureName; + private String version; + + public OperatingSystemModel(String name, String architectureName, String version) + { + this.name = name; + this.architectureName = architectureName; + this.version = version; + } + + public OperatingSystemModel() + { + this(null, null, null); + } + + public String getName() + { + return ModelUtil.nullToUnknown(name); + } + + public String getArchitectureName() + { + return ModelUtil.nullToUnknown(architectureName); + + } + + public String getVersion() + { + return ModelUtil.nullToUnknown(version); + } + +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/viewmodel/SystemBIOSModel.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/viewmodel/SystemBIOSModel.java new file mode 100644 index 0000000..a277058 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/viewmodel/SystemBIOSModel.java @@ -0,0 +1,44 @@ +package uk.co.syski.client.android.model.viewmodel; + +public class SystemBIOSModel +{ + + private String manufacturerName; + private String caption; + private String version; + private String date; + + public SystemBIOSModel(String manufacturerName, String caption, String version, String date) + { + this.manufacturerName = manufacturerName; + this.caption = caption; + this.version = version; + this.date = date; + } + + public SystemBIOSModel() + { + this(null, null, null, null); + } + + public String getManufacturerName() + { + return ModelUtil.nullToUnknown(manufacturerName); + } + + public String getCaption() + { + return ModelUtil.nullToUnknown(caption); + } + + public String getVersion() + { + return ModelUtil.nullToUnknown(version); + } + + public String getDate() + { + return ModelUtil.nullToUnknown(date); + } + +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/viewmodel/SystemCPUModel.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/viewmodel/SystemCPUModel.java new file mode 100644 index 0000000..75f72fd --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/viewmodel/SystemCPUModel.java @@ -0,0 +1,58 @@ +package uk.co.syski.client.android.model.viewmodel; + +public class SystemCPUModel { + + private String modelName; + private String manufacturerName; + private String architectureName; + private Integer clockSpeed; + private Integer coreCount; + private Integer threadCount; + + public SystemCPUModel(String modelName, String manufacturerName, String architectureName, Integer clockSpeed, Integer coreCount, Integer threadCount) + { + this.modelName = modelName; + this.manufacturerName = manufacturerName; + this.architectureName = architectureName; + this.clockSpeed = clockSpeed; + this.coreCount = coreCount; + this.threadCount = threadCount; + } + + public SystemCPUModel() + { + this(null, null, null, null, null, null); + } + + + public String getModelName() + { + return ModelUtil.nullToUnknown(modelName); + } + + public String getManufacturerName() + { + return ModelUtil.nullToUnknown(manufacturerName); + } + + public String getArchitectureName() + { + return ModelUtil.nullToUnknown(architectureName); + } + + public String getClockSpeedAsString() + { + return ModelUtil.nullToUnknown(String.valueOf(clockSpeed)); + } + + public String getCoreCountAsString() + { + return ModelUtil.nullToUnknown(String.valueOf(coreCount)); + } + + public String getThreadCountAsString() + { + return ModelUtil.nullToUnknown(String.valueOf(threadCount)); + } + +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/viewmodel/SystemGPUModel.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/viewmodel/SystemGPUModel.java new file mode 100644 index 0000000..0c8e7d6 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/viewmodel/SystemGPUModel.java @@ -0,0 +1,28 @@ +package uk.co.syski.client.android.model.viewmodel; + +public class SystemGPUModel { + + private String modelName; + private String manufacturerName; + + public SystemGPUModel(String modelName, String manufacturerName) { + this.modelName = modelName; + this.manufacturerName = manufacturerName; + } + + public SystemGPUModel() + { + this(null, null); + } + + public String getModelName() + { + return ModelUtil.nullToUnknown(modelName); + } + + public String getManufacturerName() + { + return ModelUtil.nullToUnknown(manufacturerName); + } + +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/viewmodel/SystemModel.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/viewmodel/SystemModel.java new file mode 100644 index 0000000..aafd185 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/viewmodel/SystemModel.java @@ -0,0 +1,80 @@ +package uk.co.syski.client.android.model.viewmodel; + +import java.util.Date; +import java.util.UUID; + +public class SystemModel +{ + + private UUID id; + private String hostName; + private String modelName; + private String manufacturerName; + private Date lastUpdated; + private boolean online; + private double ping; + + public SystemModel(UUID id, String hostName, String modelName, String manufacturerName) + { + this.id = id; + this.hostName = hostName; + this.modelName = modelName; + this.manufacturerName = manufacturerName; + } + + public UUID getId() + { + return id; + } + + public String getHostName() + { + return ModelUtil.nullToUnknown(hostName); + } + + public String getHostNameAndPing() + { + String result = ModelUtil.nullToUnknown(hostName); + if (online) + { + result += " (~" + Math.round(ping) + "ms)"; + } + return result; + } + + public String getModelName() + { + return ModelUtil.nullToUnknown(modelName); + } + + public String getManufacturerName() + { + return ModelUtil.nullToUnknown(manufacturerName); + } + + public boolean getOnline() + { + return online; + } + + public void setOnline() + { + online = true; + } + + public void setOffline() + { + online = false; + } + + public double getPing() + { + return ping; + } + + public void setPing(double ping) + { + this.ping = ping; + } + +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/viewmodel/SystemMotherboardModel.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/viewmodel/SystemMotherboardModel.java new file mode 100644 index 0000000..138f97b --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/viewmodel/SystemMotherboardModel.java @@ -0,0 +1,37 @@ +package uk.co.syski.client.android.model.viewmodel; + +public class SystemMotherboardModel { + + private String modelName; + private String manufacturerName; + private String version; + + public SystemMotherboardModel(String modelName, String manufacturerName, String version) + { + this.modelName = modelName; + this.manufacturerName = manufacturerName; + this.version = version; + } + + public SystemMotherboardModel() + { + this(null, null, null); + } + + public String getModelName() + { + return ModelUtil.nullToUnknown(modelName); + } + + public String getManufacturerName() + { + return ModelUtil.nullToUnknown(manufacturerName); + + } + + public String getVersion() + { + return ModelUtil.nullToUnknown(version); + } + +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/viewmodel/SystemRAMModel.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/viewmodel/SystemRAMModel.java new file mode 100644 index 0000000..cd8322b --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/viewmodel/SystemRAMModel.java @@ -0,0 +1,72 @@ +package uk.co.syski.client.android.model.viewmodel; + +public class SystemRAMModel { + + private String modelName; + private String manufacturerName; + private String memoryTypeName; + private Long memoryBytes; + + public SystemRAMModel(String modelName, String manufacturerName, String memoryTypeName, Long memoryBytes) + { + this.modelName = modelName; + this.manufacturerName = manufacturerName; + this.memoryTypeName = memoryTypeName; + this.memoryBytes = memoryBytes; + } + + public SystemRAMModel() + { + this(null, null, null, null); + } + + public String getModelName() + { + return ModelUtil.nullToUnknown(modelName); + } + + public String getManufacturerName() + { + return ModelUtil.nullToUnknown(manufacturerName); + } + + public String getMemoryTypeName() + { + return ModelUtil.nullToUnknown(memoryTypeName); + } + + public long getMemoryBytes() + { + return memoryBytes; + } + + public String getMemoryBytesAsString(String ramUnits) + { + return ModelUtil.nullToUnknown(formatBytes(memoryBytes, ramUnits)); + } + + private String formatBytes(Long bytes, String ramUnits) { + if (bytes != null) { + switch (ramUnits) { + case "TB": + bytes /= 1024; + case "GB": + bytes /= 1024; + case "MB": + bytes /= 1024; + case "KB": + bytes /= 1024; + break; + default: + return bytes + " " + "B"; + } + + return bytes + " " + ramUnits; + } + else + { + return null; + } + } + +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/viewmodel/SystemStorageModel.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/viewmodel/SystemStorageModel.java new file mode 100644 index 0000000..2971bc5 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/model/viewmodel/SystemStorageModel.java @@ -0,0 +1,68 @@ +package uk.co.syski.client.android.model.viewmodel; + +public class SystemStorageModel { + + private String modelName; + private String manufacturerName; + private String memoryTypeName; + private Long memoryBytes; + + public SystemStorageModel(String modelName, String manufacturerName, String memoryTypeName, Long memoryBytes) + { + this.modelName = modelName; + this.manufacturerName = manufacturerName; + this.memoryTypeName = memoryTypeName; + this.memoryBytes = memoryBytes; + } + + public SystemStorageModel() + { + this(null, null, null, null); + } + + public String getModelName() + { + return ModelUtil.nullToUnknown(modelName); + } + + public String getManufacturerName() + { + return ModelUtil.nullToUnknown(manufacturerName); + } + + public String getMemoryTypeName() + { + return ModelUtil.nullToUnknown(memoryTypeName); + } + + public String getMemoryBytesAsString(String storageUnits) + { + return ModelUtil.nullToUnknown(formatBytes(memoryBytes, storageUnits)); + } + + + private String formatBytes(Long bytes, String storageUnits) { + if (bytes != null) { + switch (storageUnits) { + case "TB": + bytes /= 1000; + case "GB": + bytes /= 1000; + case "MB": + bytes /= 1000; + case "KB": + bytes /= 1000; + break; + default: + return bytes + " " + "B"; + } + + return bytes + " " + storageUnits; + } + else + { + return null; + } + } + +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/activity/AppCompatPreferenceActivity.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/activity/AppCompatPreferenceActivity.java new file mode 100644 index 0000000..6baf1ba --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/activity/AppCompatPreferenceActivity.java @@ -0,0 +1,109 @@ +package uk.co.syski.client.android.view.activity; + +import android.content.res.Configuration; +import android.os.Bundle; +import android.preference.PreferenceActivity; +import android.support.annotation.LayoutRes; +import android.support.annotation.Nullable; +import android.support.v7.app.ActionBar; +import android.support.v7.app.AppCompatDelegate; +import android.support.v7.widget.Toolbar; +import android.view.MenuInflater; +import android.view.View; +import android.view.ViewGroup; + +/** + * A {@link android.preference.PreferenceActivity} which implements and proxies the necessary calls + * to be used with AppCompat. + */ +public abstract class AppCompatPreferenceActivity extends PreferenceActivity { + + private AppCompatDelegate mDelegate; + + @Override + protected void onCreate(Bundle savedInstanceState) { + getDelegate().installViewFactory(); + getDelegate().onCreate(savedInstanceState); + super.onCreate(savedInstanceState); + } + + @Override + protected void onPostCreate(Bundle savedInstanceState) { + super.onPostCreate(savedInstanceState); + getDelegate().onPostCreate(savedInstanceState); + } + + public ActionBar getSupportActionBar() { + return getDelegate().getSupportActionBar(); + } + + public void setSupportActionBar(@Nullable Toolbar toolbar) { + getDelegate().setSupportActionBar(toolbar); + } + + @Override + public MenuInflater getMenuInflater() { + return getDelegate().getMenuInflater(); + } + + @Override + public void setContentView(@LayoutRes int layoutResID) { + getDelegate().setContentView(layoutResID); + } + + @Override + public void setContentView(View view) { + getDelegate().setContentView(view); + } + + @Override + public void setContentView(View view, ViewGroup.LayoutParams params) { + getDelegate().setContentView(view, params); + } + + @Override + public void addContentView(View view, ViewGroup.LayoutParams params) { + getDelegate().addContentView(view, params); + } + + @Override + protected void onPostResume() { + super.onPostResume(); + getDelegate().onPostResume(); + } + + @Override + protected void onTitleChanged(CharSequence title, int color) { + super.onTitleChanged(title, color); + getDelegate().setTitle(title); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + getDelegate().onConfigurationChanged(newConfig); + } + + @Override + protected void onStop() { + super.onStop(); + getDelegate().onStop(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + getDelegate().onDestroy(); + } + + public void invalidateOptionsMenu() { + getDelegate().invalidateOptionsMenu(); + } + + private AppCompatDelegate getDelegate() { + if (mDelegate == null) { + mDelegate = AppCompatDelegate.create(this, null); + } + return mDelegate; + } +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/activity/BIOSActivity.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/activity/BIOSActivity.java new file mode 100644 index 0000000..98d836c --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/activity/BIOSActivity.java @@ -0,0 +1,62 @@ +package uk.co.syski.client.android.view.activity; + +import android.arch.lifecycle.Observer; +import android.arch.lifecycle.ViewModelProviders; +import android.support.annotation.Nullable; +import android.os.Bundle; +import android.widget.ListView; + +import java.util.ArrayList; + +import uk.co.syski.client.android.R; +import uk.co.syski.client.android.model.viewmodel.SystemBIOSModel; +import uk.co.syski.client.android.view.adapter.listview.HeadedValueListAdapter; +import uk.co.syski.client.android.view.fragment.DoubleHeadedValueFragment; +import uk.co.syski.client.android.view.menu.SyskiOptionsMenu; +import uk.co.syski.client.android.view.model.DoubleHeadedValueModel; +import uk.co.syski.client.android.view.model.HeadedValueModel; +import uk.co.syski.client.android.viewmodel.SystemBIOSViewModel; + +public class BIOSActivity extends SyskiActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.fragment_overview); + + optionsMenu = new SyskiOptionsMenu(); + + + + SystemBIOSViewModel viewModel = ViewModelProviders.of(this).get(SystemBIOSViewModel.class); + viewModel.get().observe(this, new Observer() { + @Override + public void onChanged(@Nullable SystemBIOSModel systemBIOSModel) { + updateStaticUI(systemBIOSModel); + } + }); + } + + private void updateStaticUI(SystemBIOSModel biosEntity) { + DoubleHeadedValueFragment topFragment = DoubleHeadedValueFragment.newInstance( + new DoubleHeadedValueModel( + R.drawable.bios_icon, + "Manufacturer", + biosEntity.getManufacturerName(), + "Caption", + biosEntity.getCaption() + ) + ); + + getSupportFragmentManager().beginTransaction().replace(R.id.topFragment, topFragment).commit(); + + ArrayList biosData = new ArrayList<>(); + + biosData.add(new HeadedValueModel(R.drawable.version_icon, "Version", biosEntity.getVersion())); + biosData.add(new HeadedValueModel(R.drawable.date_icon, "Date", biosEntity.getDate())); + + ListView dataList = findViewById(R.id.listView); + dataList.setAdapter(new HeadedValueListAdapter(this, biosData)); + } + +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/activity/CPUActivity.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/activity/CPUActivity.java new file mode 100644 index 0000000..763c170 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/activity/CPUActivity.java @@ -0,0 +1,112 @@ +package uk.co.syski.client.android.view.activity; + +import android.arch.lifecycle.Observer; +import android.arch.lifecycle.ViewModelProviders; +import android.content.Intent; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v7.app.AppCompatActivity; +import android.util.DisplayMetrics; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ExpandableListView; + +import java.util.List; + +import uk.co.syski.client.android.R; +import uk.co.syski.client.android.model.database.entity.data.CPUDataEntity; +import uk.co.syski.client.android.model.viewmodel.SystemCPUModel; +import uk.co.syski.client.android.view.activity.SyskiActivity; +import uk.co.syski.client.android.view.adapter.expandablelistview.CPUAdapter; +import uk.co.syski.client.android.view.fragment.HeadedValueFragment; +import uk.co.syski.client.android.view.graph.VariableCPULoadGraph; +import uk.co.syski.client.android.view.graph.VariableCPUProcessesGraph; +import uk.co.syski.client.android.view.menu.SyskiOptionsMenu; +import uk.co.syski.client.android.view.model.HeadedValueModel; +import uk.co.syski.client.android.viewmodel.SystemCPUDataViewModel; +import uk.co.syski.client.android.viewmodel.SystemCPUViewModel; + +/** + * Activity for displaying all CPU information for a system + */ +public class CPUActivity extends SyskiActivity { + + private static final String TAG = "CPUActivity"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_cpu); + + optionsMenu = new SyskiOptionsMenu(); + + final CPUAdapter cpuAdapter = new CPUAdapter(this); + ((ExpandableListView) findViewById(R.id.cpuList)).setAdapter(cpuAdapter); + + DisplayMetrics display = this.getResources().getDisplayMetrics(); + int width = display.widthPixels; + ExpandableListView listView = findViewById(R.id.cpuList); + listView.setIndicatorBounds(width-100, width); + + SystemCPUViewModel viewModel = ViewModelProviders.of(this).get(SystemCPUViewModel.class); + viewModel.get().observe(this, new Observer>() { + @Override + public void onChanged(@Nullable List cpuEntities) { + cpuAdapter.setData(cpuEntities); + } + }); + + SystemCPUDataViewModel realTimeViewModel = ViewModelProviders.of(this).get(SystemCPUDataViewModel.class); + realTimeViewModel.get().observe(this, new Observer>() { + @Override + public void onChanged(@Nullable List cpuEntities) { + if (cpuEntities.size() > 0) { + updateRealTimeUI(cpuEntities.get(cpuEntities.size() - 1)); + } + } + }); + + View loadFragment = findViewById(R.id.loadFragment); + loadFragment.setOnClickListener(new AdapterView.OnClickListener() { + @Override + public void onClick(View v) { + Intent cpuGraph = new Intent(v.getContext(), VariableCPULoadGraph.class); + startActivity(cpuGraph); + } + }); + + View processesFragment = findViewById(R.id.processesFragment); + processesFragment.setOnClickListener(new AdapterView.OnClickListener() { + @Override + public void onClick(View v) { + Intent cpuGraph = new Intent(v.getContext(), VariableCPUProcessesGraph.class); + startActivity(cpuGraph); + } + }); + } + + private void updateRealTimeUI(CPUDataEntity cpuDataEntity) { + HeadedValueFragment loadModel = HeadedValueFragment.newInstance( + new HeadedValueModel( + R.drawable.graph_icon, + "CPU Load", + Math.round(cpuDataEntity.Load) + "%" + ) + ); + + getSupportFragmentManager().beginTransaction().replace(R.id.loadFragment, loadModel).commit(); + + HeadedValueFragment processesModel = HeadedValueFragment.newInstance( + new HeadedValueModel( + R.drawable.graph_icon, + "CPU Processes", + Integer.toString(Math.round(cpuDataEntity.Processes)) + ) + ); + + getSupportFragmentManager().beginTransaction().replace(R.id.processesFragment, processesModel).commit(); + } +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/activity/GPUActivity.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/activity/GPUActivity.java new file mode 100644 index 0000000..17bdbda --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/activity/GPUActivity.java @@ -0,0 +1,62 @@ +package uk.co.syski.client.android.view.activity; + +import android.arch.lifecycle.Observer; +import android.arch.lifecycle.ViewModelProviders; +import android.content.Intent; +import android.support.annotation.Nullable; +import android.support.v7.app.AppCompatActivity; +import android.os.Bundle; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.widget.ListView; + +import java.util.ArrayList; +import java.util.List; + +import uk.co.syski.client.android.R; +import uk.co.syski.client.android.model.viewmodel.SystemGPUModel; +import uk.co.syski.client.android.view.activity.SyskiActivity; +import uk.co.syski.client.android.view.adapter.listview.DoubleHeadedValueListAdapter; +import uk.co.syski.client.android.view.menu.SyskiOptionsMenu; +import uk.co.syski.client.android.view.model.DoubleHeadedValueModel; +import uk.co.syski.client.android.viewmodel.SystemGPUViewModel; + +/** + * Activity for displaying all GPU information for a system + */ +public class GPUActivity extends SyskiActivity { + + private static final String TAG = "GPUActivity"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_gpu); + + optionsMenu = new SyskiOptionsMenu(); + + final DoubleHeadedValueListAdapter adapter = new DoubleHeadedValueListAdapter(this); + ListView listView = findViewById(R.id.gpuList); + listView.setAdapter(adapter); + + SystemGPUViewModel viewModel = ViewModelProviders.of(this).get(SystemGPUViewModel.class); + viewModel.get().observe(this, new Observer>() { + @Override + public void onChanged(@Nullable List gpuEntities) { + ArrayList listItems = new ArrayList<>(); + + for (int i = 0; i < gpuEntities.size(); i++) { + listItems.add( new DoubleHeadedValueModel( + R.drawable.gpu_icon, + "Model", + gpuEntities.get(i).getModelName(), + "Manufacturer", + gpuEntities.get(i).getManufacturerName() + )); + } + adapter.setData(listItems); + } + }); + } +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/activity/MOBOActivity.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/activity/MOBOActivity.java new file mode 100644 index 0000000..d71c29e --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/activity/MOBOActivity.java @@ -0,0 +1,75 @@ +package uk.co.syski.client.android.view.activity; + +import android.arch.lifecycle.Observer; +import android.arch.lifecycle.ViewModelProviders; +import android.content.Intent; +import android.os.Bundle; + +import android.support.annotation.Nullable; +import android.support.v7.app.AppCompatActivity; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.widget.ListView; + +import java.util.ArrayList; + +import uk.co.syski.client.android.R; +import uk.co.syski.client.android.model.viewmodel.SystemMotherboardModel; +import uk.co.syski.client.android.view.activity.SyskiActivity; +import uk.co.syski.client.android.view.adapter.listview.HeadedValueListAdapter; +import uk.co.syski.client.android.view.fragment.DoubleHeadedValueFragment; +import uk.co.syski.client.android.view.menu.SyskiOptionsMenu; +import uk.co.syski.client.android.view.model.DoubleHeadedValueModel; +import uk.co.syski.client.android.view.model.HeadedValueModel; +import uk.co.syski.client.android.viewmodel.MotherboardViewModel; + +/** + * Activity for displaying all Motherboard information for a system + */ +public class MOBOActivity extends SyskiActivity { + + private static final String TAG = "MOBOActivity"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.fragment_overview); + + optionsMenu = new SyskiOptionsMenu(); + + MotherboardViewModel viewModel = ViewModelProviders.of(this).get(MotherboardViewModel.class); + viewModel.get().observe(this, new Observer() { + @Override + public void onChanged(@Nullable SystemMotherboardModel motherboardEntity) { + updateStaticUI(motherboardEntity); + } + }); + } + + private void updateStaticUI(SystemMotherboardModel motherboardEntity) { + DoubleHeadedValueFragment topFragment = DoubleHeadedValueFragment.newInstance( + new DoubleHeadedValueModel( + R.drawable.motherboard_icon, + "Model", + motherboardEntity.getModelName(), + "Manufacturer", + motherboardEntity.getManufacturerName() + ) + ); + + getSupportFragmentManager().beginTransaction().replace(R.id.topFragment, topFragment).commit(); + + ArrayList motherboardData = new ArrayList<>(); + motherboardData.add( + new HeadedValueModel( + R.drawable.version_icon, + "Version", + motherboardEntity.getVersion() + ) + ); + + ListView dataList = findViewById(R.id.listView); + dataList.setAdapter(new HeadedValueListAdapter(this, motherboardData)); + } +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/activity/MainActivity.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/activity/MainActivity.java new file mode 100644 index 0000000..5829c1b --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/activity/MainActivity.java @@ -0,0 +1,373 @@ +package uk.co.syski.client.android.view.activity; + +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.Build; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.design.widget.TabLayout; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentPagerAdapter; +import android.support.v4.view.ViewPager; +import android.support.v7.app.AppCompatActivity; +import android.text.TextUtils; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.view.inputmethod.EditorInfo; +import android.widget.Button; +import android.widget.EditText; +import android.widget.TextView; + +import com.android.volley.Response; +import com.android.volley.VolleyError; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import uk.co.syski.client.android.R; +import uk.co.syski.client.android.model.api.VolleySingleton; +import uk.co.syski.client.android.model.api.requests.APIRequest; +import uk.co.syski.client.android.model.api.requests.auth.APILoginRequest; +import uk.co.syski.client.android.model.api.requests.auth.APIRegisterRequest; +import uk.co.syski.client.android.model.database.SyskiCache; +import uk.co.syski.client.android.model.repository.Repository; + + +public class MainActivity extends AppCompatActivity { + + private SectionsPagerAdapter mSectionsPagerAdapter; + private ViewPager mViewPager; + private SharedPreferences prefs; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + createNotificationChannel(); + SyskiCache.BuildDatabase(getApplicationContext()); + prefs = getApplicationContext().getSharedPreferences(getString(R.string.preference_usrID_key), Context.MODE_PRIVATE); + String userId = prefs.getString(getString(R.string.preference_usrID_key), null); + if (userId != null) { + Repository.getInstance().getUserRepository().setActiveUserId(UUID.fromString(userId)); + startActivity(new Intent(this, SystemListMenu.class)); + finish(); + } else { + setTheme(R.style.AppTheme); + setContentView(R.layout.activity_main); + + mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager()); + mSectionsPagerAdapter.addFragment(new Tab_Login(), "Login"); + mSectionsPagerAdapter.addFragment(new Tab_Register(), "Register"); + + mViewPager = findViewById(R.id.container); + mViewPager.setAdapter(mSectionsPagerAdapter); + + TabLayout tabLayout = findViewById(R.id.tabs); + + mViewPager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(tabLayout)); + tabLayout.addOnTabSelectedListener(new TabLayout.ViewPagerOnTabSelectedListener(mViewPager)); + } + } + + private void createNotificationChannel() { + // Create the NotificationChannel, but only on API 26+ because + // the NotificationChannel class is new and not in the support library + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + String CHANNEL_ID = getString(R.string.channel_id); + CharSequence name = getString(R.string.channel_name); + String description = getString(R.string.channel_description); + int importance = NotificationManager.IMPORTANCE_DEFAULT; + NotificationChannel channel = new NotificationChannel(CHANNEL_ID, name, importance); + channel.setDescription(description); + // Register the channel with the system; you can't change the importance + // or other notification behaviors after this + NotificationManager notificationManager = getSystemService(NotificationManager.class); + notificationManager.createNotificationChannel(channel); + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.sys_list_menu, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + int id = item.getItemId(); + + if (id == R.id.action_settings) { + Intent settings = new Intent(this, SettingsActivity.class); + startActivity(settings); + return true; + } + + return super.onOptionsItemSelected(item); + } + + public static class Tab_Authentication extends Fragment { + + protected boolean mDisableButton; + protected EditText mEmailView; + protected EditText mPasswordView; + + public Tab_Authentication() { + + } + + protected void sendAPIRequest(String APIRequest) { + APIRequest request = null; + if (APIRequest.equalsIgnoreCase("register")) { + request = new APIRegisterRequest(getContext(), + mEmailView.getText().toString(), + mPasswordView.getText().toString(), + new Response.Listener() { + @Override + public void onResponse(JSONObject response) { + RequestSuccessful(response); + } + }, + new Response.ErrorListener() { + @Override + public void onErrorResponse(VolleyError error) { + RequestFailed(error); + } + }); + } else if (APIRequest.equalsIgnoreCase("login")) { + request = new APILoginRequest(getContext(), + mEmailView.getText().toString(), + mPasswordView.getText().toString(), + new Response.Listener() { + @Override + public void onResponse(JSONObject response) { + RequestSuccessful(response); + } + }, + new Response.ErrorListener() { + @Override + public void onErrorResponse(VolleyError error) { + RequestFailed(error); + } + }); + } + VolleySingleton.getInstance(getActivity()).addToRequestQueue(request); + } + + protected void RequestSuccessful(JSONObject response) { + startActivity(new Intent(getActivity(), SystemListMenu.class)); + getActivity().finish(); + } + + protected void RequestFailed(VolleyError error) { + mEmailView.setError(getString(R.string.error_invalid_email)); + mPasswordView.setError(getString(R.string.error_incorrect_password)); + mPasswordView.requestFocus(); + mDisableButton = false; + } + + protected boolean isEmailValid(String email) { + return email.contains("@"); + } + + protected boolean isPasswordValid(String password) { + return password.length() > 6; + } + } + + public static class Tab_Login extends Tab_Authentication { + + public Tab_Login() { + + } + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_main_tab_login, container, false); + + mEmailView = view.findViewById(R.id.email); + mPasswordView = view.findViewById(R.id.password); + mPasswordView.setOnEditorActionListener(new TextView.OnEditorActionListener() { + @Override + public boolean onEditorAction(TextView textView, int id, KeyEvent keyEvent) { + if (id == EditorInfo.IME_ACTION_DONE || id == EditorInfo.IME_NULL) { + attemptLogin(); + return true; + } + return false; + } + }); + + Button mEmailSignInButton = view.findViewById(R.id.email_sign_in_button); + mEmailSignInButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + attemptLogin(); + } + }); + + return view; + } + + private void attemptLogin() { + if (!mDisableButton) { + mDisableButton = true; + mEmailView.setError(null); + mPasswordView.setError(null); + + final String email = mEmailView.getText().toString(); + final String password = mPasswordView.getText().toString(); + + boolean cancel = false; + View focusView = null; + + if (!TextUtils.isEmpty(password) && !isPasswordValid(password)) { + mPasswordView.setError(getString(R.string.error_invalid_password)); + focusView = mPasswordView; + cancel = true; + } + + if (TextUtils.isEmpty(email)) { + mEmailView.setError(getString(R.string.error_field_required)); + focusView = mEmailView; + cancel = true; + } else if (!isEmailValid(email)) { + mEmailView.setError(getString(R.string.error_invalid_email)); + focusView = mEmailView; + cancel = true; + } + + if (cancel) { + focusView.requestFocus(); + mDisableButton = false; + } else { + sendAPIRequest("login"); + } + } + } + + } + + public static class Tab_Register extends Tab_Authentication { + + public Tab_Register() { + + } + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_main_tab_register, container, false); + + mEmailView = view.findViewById(R.id.email); + mPasswordView = view.findViewById(R.id.password); + mPasswordView.setOnEditorActionListener(new TextView.OnEditorActionListener() { + @Override + public boolean onEditorAction(TextView textView, int id, KeyEvent keyEvent) { + if (id == EditorInfo.IME_ACTION_DONE || id == EditorInfo.IME_NULL) { + attemptRegister(); + return true; + } + return false; + } + }); + + Button mEmailSignUpButton = view.findViewById(R.id.email_sign_up_button); + mEmailSignUpButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + attemptRegister(); + } + }); + + return view; + } + + private void attemptRegister() { + if (!mDisableButton) { + mDisableButton = true; + mEmailView.setError(null); + mPasswordView.setError(null); + + final String email = mEmailView.getText().toString(); + final String password = mPasswordView.getText().toString(); + + boolean cancel = false; + View focusView = null; + + if (!TextUtils.isEmpty(password) && !isPasswordValid(password)) { + mPasswordView.setError(getString(R.string.error_invalid_password)); + focusView = mPasswordView; + cancel = true; + } + + if (TextUtils.isEmpty(email)) { + mEmailView.setError(getString(R.string.error_field_required)); + focusView = mEmailView; + cancel = true; + } else if (!isEmailValid(email)) { + mEmailView.setError(getString(R.string.error_invalid_email)); + focusView = mEmailView; + cancel = true; + } + + if (cancel) { + focusView.requestFocus(); + mDisableButton = false; + } else { + JSONObject jsonBody = new JSONObject(); + try { + jsonBody.put("email", email); + jsonBody.put("password", password); + } catch (JSONException e) { + e.printStackTrace(); + } + sendAPIRequest("register"); + } + } + } + + } + + public class SectionsPagerAdapter extends FragmentPagerAdapter { + + private final List mFragmentTitleList = new ArrayList<>(); + private final List mFragmentList = new ArrayList<>(); + + public SectionsPagerAdapter(FragmentManager fm) { + super(fm); + } + + public void addFragment(Fragment fragment, String title) { + mFragmentList.add(fragment); + mFragmentTitleList.add(title); + } + + @Override + public CharSequence getPageTitle(int position) { + return mFragmentTitleList.get(position); + } + + @Override + public Fragment getItem(int position) { + return mFragmentList.get(position); + } + + @Override + public int getCount() { + return mFragmentList.size(); + } + + } +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/activity/NFCReceiverActivity.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/activity/NFCReceiverActivity.java new file mode 100644 index 0000000..f67a2e2 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/activity/NFCReceiverActivity.java @@ -0,0 +1,136 @@ +package uk.co.syski.client.android.view.activity; + +import android.app.PendingIntent; +import android.arch.lifecycle.ViewModelProviders; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.SharedPreferences; +import android.nfc.NdefMessage; +import android.nfc.NfcAdapter; +import android.os.Bundle; +import android.os.Parcelable; +import android.preference.PreferenceManager; +import android.support.v7.app.AppCompatActivity; +import android.util.Log; +import android.widget.Toast; + +import java.io.UnsupportedEncodingException; +import java.util.List; +import java.util.UUID; + +import uk.co.syski.client.android.R; +import uk.co.syski.client.android.model.database.entity.SystemEntity; +import uk.co.syski.client.android.model.viewmodel.SystemModel; +import uk.co.syski.client.android.viewmodel.SystemListViewModel; + +/** + * Activity used when an NFC tag is scanned, parsing the scanned tag and handling its contents + * Opening the system overview if a valid system (ID) is found on the tag + * Else displaying an appropriate message + */ +public class NFCReceiverActivity extends AppCompatActivity { + + private static final String TAG = "NFCReceiverActivity"; + + private NfcAdapter nfcAdapter; + private PendingIntent pendingIntent; + private IntentFilter writeTagFilters[]; + private SharedPreferences prefs; + private SharedPreferences.Editor prefEditor; + + @Override + protected void onCreate(Bundle savedInstanceState) { + if(savedInstanceState == null){ + Intent intent = new Intent(this,NFCReceiverActivity.class); + startActivity(intent); + } + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_nfcreceiver); + + prefs = this.getSharedPreferences(getString(R.string.preference_sysID_key), Context.MODE_PRIVATE); + prefEditor = prefs.edit(); + + Log.d(TAG,"Creating NFC Adapter"); + nfcAdapter = NfcAdapter.getDefaultAdapter(this); + readFromIntent(getIntent()); + + pendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0); + IntentFilter tagDetected = new IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED); + tagDetected.addCategory(Intent.CATEGORY_DEFAULT); + writeTagFilters = new IntentFilter[] { tagDetected }; + } + + private void readFromIntent(Intent intent) { + String action = intent.getAction(); + if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(action) + || NfcAdapter.ACTION_TECH_DISCOVERED.equals(action) + || NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action)) { + Parcelable[] rawMsgs = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES); + NdefMessage[] msgs = null; + if (rawMsgs != null) { + msgs = new NdefMessage[rawMsgs.length]; + for (int i = 0; i < rawMsgs.length; i++) { + msgs[i] = (NdefMessage) rawMsgs[i]; + } + } + buildTagViews(msgs); + } + } + private void buildTagViews(NdefMessage[] msgs) { + if (msgs == null || msgs.length == 0) return; + + String text = null; + byte[] payload = msgs[0].getRecords()[0].getPayload(); + String textEncoding = ((payload[0] & 128) == 0) ? "UTF-8" : "UTF-16"; // Get the Text Encoding + int languageCodeLength = payload[0] & 0063; // Get the Language Code, e.g. "en" + + try { + // Get the Text + text = new String(payload, languageCodeLength + 1, payload.length - languageCodeLength - 1, textEncoding); + } catch (UnsupportedEncodingException e) { + Log.e(TAG, "Error occurred when encoding NFC payload"); + Log.e("UnsupportedEncoding", e.toString()); + } + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this); + if (sp.getBoolean("pref_developer_mode", Boolean.parseBoolean(getString(R.string.pref_developer_mode_default)))) + { + Toast.makeText(this, "Scanned: " +text, Toast.LENGTH_LONG).show(); + } + + try { + boolean systemNotFound = true; + UUID systemId = UUID.fromString(text); + + Log.i(TAG, "Looking up SysID using NFC"); + SystemListViewModel model = ViewModelProviders.of(this).get(SystemListViewModel.class); + List sys = model.get().getValue(); + for(SystemModel system : sys){ + if(system.getId().equals(systemId)){ + systemNotFound = false; + } + } + + if (!systemNotFound) { + Intent intent = new Intent(this, SystemOverviewActivity.class); + prefEditor.putString(getString(R.string.preference_sysID_key),systemId.toString()); + prefEditor.apply(); + startActivity(intent); + } else { + Toast.makeText(this, "Error: System does not exist", Toast.LENGTH_SHORT).show(); + Log.w(TAG,"NFC System Does Not Exist"); + redirectHome(); + } + } catch (IllegalArgumentException e) { + Toast.makeText(this, "Error: NFC Tag does not represent a system", Toast.LENGTH_SHORT).show(); + Log.w(TAG, "NFC Tag does not represent a system"); + redirectHome(); + } + } + + private void redirectHome(){ + Intent intent = new Intent(this, SystemListMenu.class); + startActivity(intent); + } + +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/activity/ProcessListActivity.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/activity/ProcessListActivity.java new file mode 100644 index 0000000..29f79d1 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/activity/ProcessListActivity.java @@ -0,0 +1,47 @@ +package uk.co.syski.client.android.view.activity; + +import android.arch.lifecycle.Observer; +import android.arch.lifecycle.ViewModelProviders; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.widget.ExpandableListView; + +import java.util.List; + +import uk.co.syski.client.android.R; +import uk.co.syski.client.android.model.database.entity.data.SystemProcessesEntity; +import uk.co.syski.client.android.view.adapter.expandablelistview.ProcessAdapter; +import uk.co.syski.client.android.view.menu.SyskiOptionsMenu; +import uk.co.syski.client.android.viewmodel.SystemProcessesViewModel; + +public class ProcessListActivity extends SyskiActivity { + + ExpandableListView processList; + ProcessAdapter adapter; + SystemProcessesViewModel viewModel; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_process_list); + + optionsMenu = new SyskiOptionsMenu(); + + processList = findViewById(R.id.processList); + adapter = new ProcessAdapter(this); + processList.setAdapter(adapter); + + viewModel = ViewModelProviders.of(this).get(SystemProcessesViewModel.class); + viewModel.get().observe(this, new Observer>() { + @Override + public void onChanged(@Nullable List systemProcessesEntities) { + adapter.setData(systemProcessesEntities); + processList.deferNotifyDataSetChanged(); + } + }); + } + + public void killProcess(SystemProcessesEntity systemProcessesEntity) { + viewModel.killProcess(systemProcessesEntity.Id); + } +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/activity/RAMActivity.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/activity/RAMActivity.java new file mode 100644 index 0000000..84b5584 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/activity/RAMActivity.java @@ -0,0 +1,88 @@ +package uk.co.syski.client.android.view.activity; + +import android.arch.lifecycle.Observer; +import android.arch.lifecycle.ViewModelProviders; +import android.content.Intent; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v7.app.AppCompatActivity; +import android.util.DisplayMetrics; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ExpandableListView; + +import java.util.List; + +import uk.co.syski.client.android.R; +import uk.co.syski.client.android.model.database.entity.data.RAMDataEntity; +import uk.co.syski.client.android.model.viewmodel.SystemRAMModel; +import uk.co.syski.client.android.view.activity.SyskiActivity; +import uk.co.syski.client.android.view.adapter.expandablelistview.RAMAdapter; +import uk.co.syski.client.android.view.fragment.HeadedValueFragment; +import uk.co.syski.client.android.view.graph.VariableRAMGraph; +import uk.co.syski.client.android.view.menu.SyskiOptionsMenu; +import uk.co.syski.client.android.view.model.HeadedValueModel; +import uk.co.syski.client.android.viewmodel.SystemRAMDataViewModel; +import uk.co.syski.client.android.viewmodel.SystemRAMViewModel; + +/** + * Activity for displaying all RAM information for a system + */ +public class RAMActivity extends SyskiActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_ram); + + optionsMenu = new SyskiOptionsMenu(); + + final RAMAdapter adapter = new RAMAdapter(this); + ((ExpandableListView) findViewById(R.id.listView)).setAdapter(adapter); + + DisplayMetrics display = this.getResources().getDisplayMetrics(); + int width = display.widthPixels; + ExpandableListView listView = findViewById(R.id.listView); + listView.setIndicatorBounds(width-125, width-25); + + SystemRAMViewModel model = ViewModelProviders.of(this).get(SystemRAMViewModel.class); + model.get().observe(this, new Observer>() { + @Override + public void onChanged(@Nullable List ramEntities) { + adapter.setData(ramEntities); + } + }); + + SystemRAMDataViewModel viewModel = ViewModelProviders.of(this).get(SystemRAMDataViewModel.class); + viewModel.get().observe(this, new Observer>() { + @Override + public void onChanged(@Nullable List ramEntities) { + updateRealTimeUI(ramEntities.get(ramEntities.size() - 1)); + } + }); + + View freeRAMFragment = findViewById(R.id.freeRAMFragment); + freeRAMFragment.setOnClickListener(new AdapterView.OnClickListener() { + @Override + public void onClick(View v) { + Intent ramGraph = new Intent(v.getContext(), VariableRAMGraph.class); + startActivity(ramGraph); + } + }); + } + + private void updateRealTimeUI(RAMDataEntity ramDataEntity) { + HeadedValueFragment freeRAMFragment = HeadedValueFragment.newInstance( + new HeadedValueModel( + R.drawable.graph_icon, + "Free RAM", + ramDataEntity.Free + "MB" + ) + ); + + getSupportFragmentManager().beginTransaction().replace(R.id.freeRAMFragment, freeRAMFragment).commit(); + } +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/activity/SettingsActivity.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/activity/SettingsActivity.java new file mode 100644 index 0000000..56ae29e --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/activity/SettingsActivity.java @@ -0,0 +1,339 @@ +package uk.co.syski.client.android.view.activity; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.Intent; +import android.content.res.Configuration; +import android.media.Ringtone; +import android.media.RingtoneManager; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.preference.ListPreference; +import android.preference.Preference; +import android.preference.PreferenceActivity; +import android.support.v7.app.ActionBar; +import android.preference.PreferenceFragment; +import android.preference.PreferenceManager; +import android.preference.RingtonePreference; +import android.text.TextUtils; +import android.view.MenuItem; + +import java.util.List; + +import uk.co.syski.client.android.R; + +/** + * A {@link PreferenceActivity} that presents a set of application settings. On + * handset devices, settings are presented as a single list. On tablets, + * settings are split by category, with category headers shown to the left of + * the list of settings. + *

+ * See + * Android Design: Settings for design guidelines and the Settings + * API Guide for more information on developing a Settings UI. + */ +public class SettingsActivity extends AppCompatPreferenceActivity { + + /** + * A preference value change listener that updates the preference's summary + * to reflect its new value. + */ + private static Preference.OnPreferenceChangeListener sBindPreferenceSummaryToValueListener = new Preference.OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object value) { + String stringValue = value.toString(); + + if (preference instanceof ListPreference) { + // For list preferences, look up the correct display value in + // the preference's 'entries' list. + ListPreference listPreference = (ListPreference) preference; + int index = listPreference.findIndexOfValue(stringValue); + + // Set the summary to reflect the new value. + preference.setSummary( + index >= 0 + ? listPreference.getEntries()[index] + : null); + + } else if (preference instanceof RingtonePreference) { + // For ringtone preferences, look up the correct display value + // using RingtoneManager. + if (TextUtils.isEmpty(stringValue)) { + // Empty values correspond to 'silent' (no ringtone). + preference.setSummary(R.string.pref_ringtone_silent); + + } else { + Ringtone ringtone = RingtoneManager.getRingtone( + preference.getContext(), Uri.parse(stringValue)); + + if (ringtone == null) { + // Clear the summary if there was a lookup error. + preference.setSummary(null); + } else { + // Set the summary to reflect the new ringtone display + // name. + String name = ringtone.getTitle(preference.getContext()); + preference.setSummary(name); + } + } + + } else { + // For all other preferences, set the summary to the value's + // simple string representation. + preference.setSummary(stringValue); + } + return true; + } + }; + + /** + * Helper method to determine if the device has an extra-large screen. For + * example, 10" tablets are extra-large. + */ + private static boolean isXLargeTablet(Context context) { + return (context.getResources().getConfiguration().screenLayout + & Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_XLARGE; + } + + /** + * Binds a preference's summary to its value. More specifically, when the + * preference's value is changed, its summary (line of text below the + * preference title) is updated to reflect the value. The summary is also + * immediately updated upon calling this method. The exact display format is + * dependent on the type of preference. + * + * @see #sBindPreferenceSummaryToValueListener + */ + private static void bindPreferenceSummaryToValue(Preference preference) { + // Set the listener to watch for value changes. + preference.setOnPreferenceChangeListener(sBindPreferenceSummaryToValueListener); + + // Trigger the listener immediately with the preference's + // current value. + sBindPreferenceSummaryToValueListener.onPreferenceChange(preference, + PreferenceManager + .getDefaultSharedPreferences(preference.getContext()) + .getString(preference.getKey(), "")); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setupActionBar(); + } + + /** + * Set up the {@link android.app.ActionBar}, if the API is available. + */ + private void setupActionBar() { + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + // Show the Up button in the action bar. + actionBar.setDisplayHomeAsUpEnabled(true); + } + } + + public boolean onOptionsItemSelected(MenuItem menu) { + switch (menu.getItemId()) + { + case 16908332: finish(); + return true; + } + return super.onOptionsItemSelected(menu); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean onIsMultiPane() { + return isXLargeTablet(this); + } + + /** + * {@inheritDoc} + */ + @Override + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + public void onBuildHeaders(List

target) { + loadHeadersFromResource(R.xml.pref_headers, target); + } + + /** + * This method stops fragment injection in malicious applications. + * Make sure to deny any unknown fragments here. + */ + protected boolean isValidFragment(String fragmentName) { + return PreferenceFragment.class.getName().equals(fragmentName) + || GeneralPreferenceFragment.class.getName().equals(fragmentName) + || DataSyncPreferenceFragment.class.getName().equals(fragmentName) + || NotificationPreferenceFragment.class.getName().equals(fragmentName) + || APIPreferenceFragment.class.getName().equals(fragmentName) + || DeveloperPreferenceFragment.class.getName().equals(fragmentName) + || GenPreferenceFragment.class.getName().equals(fragmentName); + } + + /** + * This fragment shows general preferences only. It is used when the + * activity is showing a two-pane settings UI. + */ + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + public static class GeneralPreferenceFragment extends PreferenceFragment { + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.pref_general); + setHasOptionsMenu(true); + + // Bind the summaries of EditText/List/Dialog/Ringtone preferences + // to their values. When their values change, their summaries are + // updated to reflect the new value, per the Android Design + // guidelines. + bindPreferenceSummaryToValue(findPreference("example_text")); + bindPreferenceSummaryToValue(findPreference("example_list")); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + int id = item.getItemId(); + if (id == android.R.id.home) { + startActivity(new Intent(getActivity(), SettingsActivity.class)); + return true; + } + return super.onOptionsItemSelected(item); + } + } + + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + public static class GenPreferenceFragment extends PreferenceFragment { + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.pref_gen); + setHasOptionsMenu(true); + + // Bind the summaries of EditText/List/Dialog/Ringtone preferences + // to their values. When their values change, their summaries are + // updated to reflect the new value, per the Android Design + // guidelines. + bindPreferenceSummaryToValue(findPreference(getString(R.string.pref_general_ram_unit))); + bindPreferenceSummaryToValue(findPreference(getString(R.string.pref_general_storage_unit))); + } + } + + /** + * This fragment shows notification preferences only. It is used when the + * activity is showing a two-pane settings UI. + */ + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + public static class NotificationPreferenceFragment extends PreferenceFragment { + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.pref_notification); + setHasOptionsMenu(true); + + // Bind the summaries of EditText/List/Dialog/Ringtone preferences + // to their values. When their values change, their summaries are + // updated to reflect the new value, per the Android Design + // guidelines. + bindPreferenceSummaryToValue(findPreference("notifications_new_message_ringtone")); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + int id = item.getItemId(); + if (id == android.R.id.home) { + startActivity(new Intent(getActivity(), SettingsActivity.class)); + return true; + } + return super.onOptionsItemSelected(item); + } + } + + + /** + * This fragment shows data and sync preferences only. It is used when the + * activity is showing a two-pane settings UI. + */ + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + public static class DataSyncPreferenceFragment extends PreferenceFragment { + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.pref_data_sync); + setHasOptionsMenu(true); + + // Bind the summaries of EditText/List/Dialog/Ringtone preferences + // to their values. When their values change, their summaries are + // updated to reflect the new value, per the Android Design + // guidelines. + bindPreferenceSummaryToValue(findPreference("sync_frequency")); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + int id = item.getItemId(); + if (id == android.R.id.home) { + startActivity(new Intent(getActivity(), SettingsActivity.class)); + return true; + } + return super.onOptionsItemSelected(item); + } + } + + public static class APIPreferenceFragment extends PreferenceFragment { + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.pref_api); + setHasOptionsMenu(true); + + // Bind the summaries of EditText/List/Dialog/Ringtone preferences + // to their values. When their values change, their summaries are + // updated to reflect the new value, per the Android Design + // guidelines. + bindPreferenceSummaryToValue(findPreference(getString(R.string.pref_api_url))); + bindPreferenceSummaryToValue(findPreference(getString(R.string.pref_api_port))); + bindPreferenceSummaryToValue(findPreference(getString(R.string.pref_api_path))); + bindPreferenceSummaryToValue(findPreference(getString(R.string.pref_api_refreshinterval))); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + int id = item.getItemId(); + if (id == android.R.id.home) { + startActivity(new Intent(getActivity(), SettingsActivity.class)); + return true; + } + return super.onOptionsItemSelected(item); + } + } + + public static class DeveloperPreferenceFragment extends PreferenceFragment { + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.pref_developer); + setHasOptionsMenu(true); + + // Bind the summaries of EditText/List/Dialog/Ringtone preferences + // to their values. When their values change, their summaries are + // updated to reflect the new value, per the Android Design + // guidelines. + //bindPreferenceSummaryToValue(findPreference("notifications_new_message_ringtone")); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + int id = item.getItemId(); + if (id == android.R.id.home) { + startActivity(new Intent(getActivity(), SettingsActivity.class)); + return true; + } + return super.onOptionsItemSelected(item); + } + } +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/activity/SyskiActivity.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/activity/SyskiActivity.java new file mode 100644 index 0000000..5ec1f22 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/activity/SyskiActivity.java @@ -0,0 +1,23 @@ +package uk.co.syski.client.android.view.activity; + +import android.support.v7.app.AppCompatActivity; +import android.view.Menu; +import android.view.MenuItem; + +import uk.co.syski.client.android.view.menu.ActivityOptionsMenu; + +public abstract class SyskiActivity extends AppCompatActivity { + + protected ActivityOptionsMenu optionsMenu; + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + return optionsMenu.create(menu, getMenuInflater()); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + return optionsMenu.onItemSelected(item, this); + } + +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/activity/SystemListMenu.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/activity/SystemListMenu.java new file mode 100644 index 0000000..02333da --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/activity/SystemListMenu.java @@ -0,0 +1,241 @@ +package uk.co.syski.client.android.view.activity; + +import android.app.PendingIntent; +import android.arch.lifecycle.Observer; +import android.arch.lifecycle.ViewModelProviders; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.design.widget.NavigationView; +import android.support.design.widget.Snackbar; +import android.support.v4.view.GravityCompat; +import android.support.v4.widget.DrawerLayout; +import android.support.v7.app.ActionBarDrawerToggle; +import android.support.v7.widget.GridLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.Toolbar; +import android.support.v7.widget.helper.ItemTouchHelper; +import android.util.Log; +import android.view.MenuItem; +import android.widget.Toast; + +import com.google.android.gms.tasks.OnCompleteListener; +import com.google.android.gms.tasks.Task; +import com.google.firebase.iid.FirebaseInstanceId; +import com.google.firebase.iid.InstanceIdResult; +import com.google.zxing.integration.android.IntentIntegrator; +import com.google.zxing.integration.android.IntentResult; + +import java.util.List; +import java.util.UUID; + +import uk.co.syski.client.android.R; +import uk.co.syski.client.android.model.database.SyskiCache; +import uk.co.syski.client.android.model.database.entity.SystemEntity; +import uk.co.syski.client.android.model.repository.Repository; +import uk.co.syski.client.android.model.viewmodel.SystemModel; +import uk.co.syski.client.android.view.adapter.recyclerview.SystemListAdapter; +import uk.co.syski.client.android.view.menu.SystemListOptionsMenu; +import uk.co.syski.client.android.viewmodel.SystemListViewModel; + +/** + * Activity displaying a list of a user's systems + */ +public class SystemListMenu extends SyskiActivity implements NavigationView.OnNavigationItemSelectedListener { + + private static final String TAG = "SystemListMenu"; + SharedPreferences prefs; + SharedPreferences.Editor prefEditor; + + private SystemListViewModel viewModel; + private List systemEntityList; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_sys_list_menu); + + optionsMenu = new SystemListOptionsMenu(); + + //Setup toolbar + Toolbar toolbar = findViewById(R.id.toolbar); + setSupportActionBar(toolbar); + + //Setup drawer + DrawerLayout drawer = findViewById(R.id.drawer_layout); + ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close); + drawer.addDrawerListener(toggle); + toggle.syncState(); + + NavigationView navigationView = findViewById(R.id.nav_view); + navigationView.setNavigationItemSelectedListener(this); + + prefs = this.getSharedPreferences(getString(R.string.preference_sysID_key), Context.MODE_PRIVATE); + prefEditor = prefs.edit(); + + // Setup ListView + final RecyclerView listView = findViewById(R.id.sysList); + + listView.setLayoutManager(new GridLayoutManager(this, 1)); + + final SystemListAdapter adapter = new SystemListAdapter(this); + listView.setAdapter(adapter); + + viewModel = ViewModelProviders.of(this).get(SystemListViewModel.class); + viewModel.get().observe(this, new Observer>() { + @Override + public void onChanged(@Nullable List systemEntities) { + adapter.setData(systemEntities); + } + }); + + ItemTouchHelper.SimpleCallback systemListCallback = new ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT) { + @Override + public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) { + return false; + } + + @Override + public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) { + // TODO: Delete System from API + SystemModel systemEntity = adapter.getItem(viewHolder.getAdapterPosition()); + + viewModel.delete(systemEntity.getId()); + adapter.removeItem(viewHolder.getAdapterPosition()); + + // TODO: Snackbar Undo Functionality + Snackbar snackbar = Snackbar.make(listView,"System: \""+systemEntity.getHostName()+"\" Deleted", Snackbar.LENGTH_LONG); + snackbar.show(); + } + }; + + new ItemTouchHelper(systemListCallback).attachToRecyclerView(listView); + + Intent intent = new Intent(this, SystemListMenu.class); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0); + + + + //Firebase + FirebaseInstanceId.getInstance().getInstanceId() + .addOnCompleteListener(new OnCompleteListener() { + @Override + public void onComplete(@NonNull Task task) { + if (!task.isSuccessful()) { + Log.w(TAG, "getInstanceId failed", task.getException()); + return; + } + + // Get new Instance ID token + String token = task.getResult().getToken(); + + // Log and toast + String msg = getString(R.string.msg_token_fmt, token); + Log.d(TAG, "FCM " + msg); + //Toast.makeText(SystemListMenu.this, msg, Toast.LENGTH_SHORT).show(); + } + }); + + } + + @Override + public void onBackPressed() { + DrawerLayout drawer = findViewById(R.id.drawer_layout); + if (drawer.isDrawerOpen(GravityCompat.START)) { + drawer.closeDrawer(GravityCompat.START); + } else { + super.onBackPressed(); + } + } + + @SuppressWarnings("StatementWithEmptyBody") + @Override + public boolean onNavigationItemSelected(MenuItem item) { + // Handle navigation view item clicks here. + int id = item.getItemId(); + + if (id == R.id.nav_qr) { + initQR(); + } else if (id == R.id.nav_settings) { + //Handle Settings + Intent settings = new Intent(this, SettingsActivity.class); + startActivity(settings); + } else if (id == R.id.nav_logout) { + new Thread(new Runnable() { + @Override + public void run() { + SyskiCache.GetDatabase().clearAllTables(); + } + }).start(); + Repository.getInstance().getUserRepository().setActiveUserId(null); + prefEditor.remove(getString(R.string.preference_sysID_key)).commit(); + getSharedPreferences(getString(R.string.preference_usrID_key), Context.MODE_PRIVATE).edit().remove(getString(R.string.preference_usrID_key)).commit(); + finish(); + startActivity(new Intent(this, MainActivity.class)); + } + + DrawerLayout drawer = findViewById(R.id.drawer_layout); + drawer.closeDrawer(GravityCompat.START); + //Unhighlight item + return false; + } + + private void initQR() { + IntentIntegrator integrator = new IntentIntegrator(this); + integrator.setDesiredBarcodeFormats(IntentIntegrator.QR_CODE); + integrator.setPrompt("Scan a QR Code"); + integrator.setCameraId(0); + integrator.setOrientationLocked(false); + integrator.setBeepEnabled(false); + integrator.initiateScan(); + } + + + public void onActivityResult(int requestCode, int resultCode, Intent data) { + IntentResult result = IntentIntegrator.parseActivityResult(requestCode, resultCode, data); + if (result != null) { + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this); + if (result.getContents() == null) { + if (sp.getBoolean("pref_developer_mode", Boolean.parseBoolean(getString(R.string.pref_developer_mode_default)))) { + Toast.makeText(this, "Scan Cancelled", Toast.LENGTH_LONG).show(); + } + } else { + if (sp.getBoolean("pref_developer_mode", Boolean.parseBoolean(getString(R.string.pref_developer_mode_default)))) { + Toast.makeText(this, "Scanned: " + result.getContents(), Toast.LENGTH_LONG).show(); + } + try { + boolean systemFound = false; + UUID systemId = UUID.fromString(result.getContents()); + + List sys = viewModel.get().getValue(); + if (sys != null) + { + for(SystemModel system : sys){ + if(system.getId().equals(systemId)){ + systemFound = true; + } + } + } + + if (systemFound) { + Intent intent = new Intent(this, SystemOverviewActivity.class); + prefEditor.putString(getString(R.string.preference_sysID_key), systemId.toString()); + prefEditor.apply(); + startActivity(intent); + } else { + Toast.makeText(this, "Error: System does not exist", Toast.LENGTH_LONG).show(); + } + } catch (IllegalArgumentException e) { + Toast.makeText(this, "Error: QR Code does not represent a system", Toast.LENGTH_LONG).show(); + } + } + } else { + super.onActivityResult(requestCode, resultCode, data); + } + } +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/activity/SystemOSActivity.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/activity/SystemOSActivity.java new file mode 100644 index 0000000..36e3334 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/activity/SystemOSActivity.java @@ -0,0 +1,70 @@ +package uk.co.syski.client.android.view.activity; + +import android.arch.lifecycle.Observer; +import android.arch.lifecycle.ViewModelProviders; +import android.content.Intent; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v7.app.AppCompatActivity; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.widget.ListView; + +import java.util.ArrayList; +import java.util.List; + +import uk.co.syski.client.android.R; +import uk.co.syski.client.android.model.viewmodel.OperatingSystemModel; +import uk.co.syski.client.android.view.activity.SyskiActivity; +import uk.co.syski.client.android.view.adapter.listview.HeadedValueListAdapter; +import uk.co.syski.client.android.view.menu.SyskiOptionsMenu; +import uk.co.syski.client.android.view.model.HeadedValueModel; +import uk.co.syski.client.android.viewmodel.OperatingSystemViewModel; + +/** + * Activity for displaying all Operating System information for a system + */ +public class SystemOSActivity extends SyskiActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_os); + + optionsMenu = new SyskiOptionsMenu(); + + OperatingSystemViewModel viewModel = ViewModelProviders.of(this).get(OperatingSystemViewModel.class); + viewModel.get().observe(this, new Observer>() { + @Override + public void onChanged(@Nullable List osEntities) { + if (osEntities != null && osEntities.size() > 0) { + updateStaticUI(osEntities.get(0)); + } + } + }); + } + + private void updateStaticUI(OperatingSystemModel operatingSystemModel) { + ArrayList osData = new ArrayList<>(); + + osData.add(new HeadedValueModel( + R.drawable.name_icon, + "Name", + operatingSystemModel.getName() + )); + osData.add(new HeadedValueModel( + R.drawable.cpu_architecture_icon, + "Architecture", + operatingSystemModel.getArchitectureName() + )); + osData.add(new HeadedValueModel( + R.drawable.version_icon, + "Version", + operatingSystemModel.getVersion() + )); + + ListView listView = findViewById(R.id.listView); + listView.setAdapter(new HeadedValueListAdapter(this, osData)); + } +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/activity/SystemOverviewActivity.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/activity/SystemOverviewActivity.java new file mode 100644 index 0000000..548d9e3 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/activity/SystemOverviewActivity.java @@ -0,0 +1,229 @@ +package uk.co.syski.client.android.view.activity; + +import android.arch.lifecycle.Observer; +import android.arch.lifecycle.ViewModelProviders; +import android.content.Intent; +import android.support.annotation.Nullable; +import android.support.constraint.ConstraintLayout; +import android.os.Bundle; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ListView; + +import java.util.ArrayList; +import java.util.List; + +import uk.co.syski.client.android.R; +import uk.co.syski.client.android.model.database.entity.SystemEntity; +import uk.co.syski.client.android.model.viewmodel.SystemModel; +import uk.co.syski.client.android.view.adapter.listview.HeadedValueListAdapter; +import uk.co.syski.client.android.view.fragment.HeadedValueFragment; +import uk.co.syski.client.android.view.fragment.OverviewFragment; +import uk.co.syski.client.android.view.menu.SyskiOptionsMenu; +import uk.co.syski.client.android.view.model.DoubleHeadedValueModel; +import uk.co.syski.client.android.view.model.HeadedValueModel; +import uk.co.syski.client.android.viewmodel.SystemSummaryViewModel; + +/** + * Activity for displaying an overview of a specified system + */ +public class SystemOverviewActivity extends SyskiActivity { + + private static final String TAG = "SystemOverviewActivity"; + private SystemSummaryViewModel viewModel; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_system_overview); + + optionsMenu = new SyskiOptionsMenu(); + + buildBaseUI(); + + viewModel = ViewModelProviders.of(this).get(SystemSummaryViewModel.class); + viewModel.get().observe(this, new Observer() { + @Override + public void onChanged(@Nullable SystemModel systemEntity) { + updateStaticUI(systemEntity); + } + }); + } + + private void buildBaseUI() { + ListView listView = findViewById(R.id.compList); + listView.setAdapter(new HeadedValueListAdapter(this, getComponentList())); + + listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + openComponentOverview(position); + } + }); + + HeadedValueFragment shutdownModel = HeadedValueFragment.newInstance(new HeadedValueModel(R.drawable.shutdown_icon, "Shutdown System", "Tap here")); + getSupportFragmentManager().beginTransaction().add(R.id.shutdownFragment, shutdownModel).commit(); + + View shutdownFragment = findViewById(R.id.shutdownFragment); + shutdownFragment.setOnClickListener(new AdapterView.OnClickListener() { + @Override + public void onClick(View v) { + shutdownOnClick(v); + } + }); + + HeadedValueFragment restartModel = HeadedValueFragment.newInstance(new HeadedValueModel(R.drawable.restart_icon, "Restart System", "Tap here")); + getSupportFragmentManager().beginTransaction().add(R.id.restartFragment, restartModel).commit(); + + View restartFragment = findViewById(R.id.restartFragment); + restartFragment.setOnClickListener(new AdapterView.OnClickListener() { + @Override + public void onClick(View v) { + restartOnClick(v); + } + }); + } + + private void updateStaticUI(SystemModel systemEntity) { + ArrayList systemData = new ArrayList<>(); + + systemData.add( + new HeadedValueModel( + R.drawable.name_icon, + "Host Name", + systemEntity.getHostName() + ) + ); + + int icon; + if (systemEntity.getOnline()) + { + icon = R.drawable.online_pc_icon; + + systemData.add( + new HeadedValueModel( + R.drawable.ping_icon, + "Last Ping", + Math.round(systemEntity.getPing()) + " ms" + ) + ); + } + else + { + icon = R.drawable.offline_pc_icon; + } + OverviewFragment overviewFragment = OverviewFragment.newInstance( + new DoubleHeadedValueModel( + icon, + "Model", + systemEntity.getModelName(), + "Manufacturer", + systemEntity.getManufacturerName() + ), + systemData + ); + + getSupportFragmentManager().beginTransaction().replace(R.id.overviewFragment, overviewFragment).commit(); + + findViewById(R.id.overviewFragment).setLayoutParams(new ConstraintLayout.LayoutParams(ConstraintLayout.LayoutParams.MATCH_PARENT, ConstraintLayout.LayoutParams.WRAP_CONTENT)); + } + + private void openComponentOverview(int position) { + Class dest; + switch (position) { + case 0: dest = CPUActivity.class; + break; + case 1: dest = RAMActivity.class; + break; + case 2: dest = SystemStorageActivity.class; + break; + case 3: dest = GPUActivity.class; + break; + case 4: dest = MOBOActivity.class; + break; + case 5: dest = BIOSActivity.class; + break; + case 6: dest = SystemOSActivity.class; + break; + case 7: dest = ProcessListActivity.class; + break; + default: dest = null; + } + + Intent intent = new Intent(uk.co.syski.client.android.view.activity.SystemOverviewActivity.this, dest); + startActivity(intent); + } + + private List getComponentList() { + ArrayList listItems = new ArrayList<>(); + + listItems.add( + new HeadedValueModel( + R.drawable.cpu_icon, + "View details for", + "CPU" + ) + ); + listItems.add( + new HeadedValueModel( + R.drawable.ram_icon, + "View details for", + "RAM" + ) + ); + listItems.add( + new HeadedValueModel( + R.drawable.storage_icon, + "View details for", + "Storage" + ) + ); + listItems.add( + new HeadedValueModel( + R.drawable.gpu_icon, + "View details for", + "GPU" + ) + ); + listItems.add( + new HeadedValueModel( + R.drawable.motherboard_icon, + "View details for", + "Motherboard" + ) + ); + listItems.add( + new HeadedValueModel( + R.drawable.bios_icon, + "View details for", + "BIOS" + ) + ); + listItems.add( + new HeadedValueModel( + R.drawable.pc_icon, + "View details for", + "Operating System" + ) + ); + listItems.add( + new HeadedValueModel( + R.drawable.process_icon, + "View details of", + "Processes" + ) + ); + + return listItems; + } + + + public void shutdownOnClick(View v) { + viewModel.shutdownOnClick(); + } + + public void restartOnClick(View v) { + viewModel.restartOnClick(); + } + +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/activity/SystemStorageActivity.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/activity/SystemStorageActivity.java new file mode 100644 index 0000000..4ac9cb8 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/activity/SystemStorageActivity.java @@ -0,0 +1,154 @@ +package uk.co.syski.client.android.view.activity; + +import android.arch.lifecycle.Observer; +import android.arch.lifecycle.ViewModelProviders; +import android.content.Intent; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v7.app.AppCompatActivity; +import android.util.DisplayMetrics; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ExpandableListView; + +import java.util.List; + +import uk.co.syski.client.android.R; +import uk.co.syski.client.android.model.database.entity.data.StorageDataEntity; +import uk.co.syski.client.android.model.viewmodel.SystemStorageModel; +import uk.co.syski.client.android.view.activity.SyskiActivity; +import uk.co.syski.client.android.view.adapter.expandablelistview.StorageAdapter; +import uk.co.syski.client.android.view.fragment.DoubleHeadedValueFragment; +import uk.co.syski.client.android.view.fragment.HeadedValueFragment; +import uk.co.syski.client.android.view.graph.VariableStorageByteReadWriteGraph; +import uk.co.syski.client.android.view.graph.VariableStorageReadWriteGraph; +import uk.co.syski.client.android.view.graph.VariableStorageTimeGraph; +import uk.co.syski.client.android.view.graph.VariableStorageTransfersGraph; +import uk.co.syski.client.android.view.menu.SyskiOptionsMenu; +import uk.co.syski.client.android.view.model.DoubleHeadedValueModel; +import uk.co.syski.client.android.view.model.HeadedValueModel; +import uk.co.syski.client.android.viewmodel.SystemStorageDataViewModel; +import uk.co.syski.client.android.viewmodel.SystemStorageViewModel; + +/** + * Activity for displaying all storage information for a system + */ +public class SystemStorageActivity extends SyskiActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_storage); + + optionsMenu = new SyskiOptionsMenu(); + + final StorageAdapter adapter = new StorageAdapter(this); + ((ExpandableListView) findViewById(R.id.listView)).setAdapter(adapter); + + DisplayMetrics display = this.getResources().getDisplayMetrics(); + int width = display.widthPixels; + ExpandableListView listView = findViewById(R.id.listView); + listView.setIndicatorBounds(width-125, width-25); + + SystemStorageViewModel model = ViewModelProviders.of(this).get(SystemStorageViewModel.class); + model.get().observe(this, new Observer>() { + @Override + public void onChanged(@Nullable List StorageEntities) { + adapter.setData(StorageEntities); + } + }); + + SystemStorageDataViewModel viewModel = ViewModelProviders.of(this).get(SystemStorageDataViewModel.class); + viewModel.get().observe(this, new Observer>() { + @Override + public void onChanged(@Nullable List StorageEntities) { + updateRealTimeUI(StorageEntities.get(StorageEntities.size() - 1)); + } + }); + + View timeFragment = findViewById(R.id.timeFragment); + timeFragment.setOnClickListener(new AdapterView.OnClickListener() { + @Override + public void onClick(View v) { + Intent timeGraph = new Intent(v.getContext(), VariableStorageTimeGraph.class); + startActivity(timeGraph); + } + }); + + View transfersFragment = findViewById(R.id.transfersFragment); + transfersFragment.setOnClickListener(new AdapterView.OnClickListener() { + @Override + public void onClick(View v) { + Intent transfersGraph = new Intent(v.getContext(), VariableStorageTransfersGraph.class); + startActivity(transfersGraph); + } + }); + + View readsWritesFragment = findViewById(R.id.readsWritesFragment); + readsWritesFragment.setOnClickListener(new AdapterView.OnClickListener() { + @Override + public void onClick(View v) { + Intent readsWritesGraph = new Intent(v.getContext(), VariableStorageReadWriteGraph.class); + startActivity(readsWritesGraph); + } + }); + + View byteReadsWritesFragment = findViewById(R.id.byteReadsWritesFragment); + byteReadsWritesFragment.setOnClickListener(new AdapterView.OnClickListener() { + @Override + public void onClick(View v) { + Intent byteReadsWritesGraph = new Intent(v.getContext(), VariableStorageByteReadWriteGraph.class); + startActivity(byteReadsWritesGraph); + } + }); + } + + private void updateRealTimeUI(StorageDataEntity StorageDataEntity) { + HeadedValueFragment timeFragment = HeadedValueFragment.newInstance( + new HeadedValueModel( + R.drawable.graph_icon, + "Time", + StorageDataEntity.Time + "s" + ) + ); + + getSupportFragmentManager().beginTransaction().replace(R.id.timeFragment, timeFragment).commit(); + + HeadedValueFragment transfersFragment = HeadedValueFragment.newInstance( + new HeadedValueModel( + R.drawable.graph_icon, + "Transfers", + Float.toString(StorageDataEntity.Transfers) + ) + ); + + getSupportFragmentManager().beginTransaction().replace(R.id.transfersFragment, transfersFragment).commit(); + + DoubleHeadedValueFragment readsWritesFragment = DoubleHeadedValueFragment.newInstance( + new DoubleHeadedValueModel( + R.drawable.graph_icon, + "Reads", + Float.toString(StorageDataEntity.Reads), + "Writes", + Float.toString(StorageDataEntity.Writes) + ) + ); + + getSupportFragmentManager().beginTransaction().replace(R.id.readsWritesFragment, readsWritesFragment).commit(); + + DoubleHeadedValueFragment byteReadsWritesFragment = DoubleHeadedValueFragment.newInstance( + new DoubleHeadedValueModel( + R.drawable.graph_icon, + "Byte Reads", + Float.toString(StorageDataEntity.ByteReads), + "Byte Writes", + Float.toString(StorageDataEntity.ByteWrites) + ) + ); + + getSupportFragmentManager().beginTransaction().replace(R.id.byteReadsWritesFragment, byteReadsWritesFragment).commit(); + } +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/adapter/expandablelistview/CPUAdapter.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/adapter/expandablelistview/CPUAdapter.java new file mode 100644 index 0000000..ba7799c --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/adapter/expandablelistview/CPUAdapter.java @@ -0,0 +1,136 @@ +package uk.co.syski.client.android.view.adapter.expandablelistview; + +import android.app.Activity; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseExpandableListAdapter; +import android.widget.ExpandableListView; +import android.widget.ImageView; +import android.widget.TextView; + +import java.util.LinkedList; +import java.util.List; + +import uk.co.syski.client.android.R; +import uk.co.syski.client.android.model.viewmodel.SystemCPUModel; + +public class CPUAdapter extends BaseExpandableListAdapter { + + private Activity context; + private List cpuModelEntities; + + public CPUAdapter(Activity context) { + this.context = context; + this.cpuModelEntities = new LinkedList<>(); + } + + public void setData(List cpuModelEntities) { + this.cpuModelEntities = cpuModelEntities; + notifyDataSetChanged(); + } + + @Override + public int getGroupCount() { + return cpuModelEntities.size(); + } + + @Override + public int getChildrenCount(int groupPosition) { + return 4; + } + + @Override + public Object getGroup(int groupPosition) { + return cpuModelEntities.get(groupPosition); + } + + @Override + public Object getChild(int groupPosition, int childPosition) { + return cpuModelEntities.get(groupPosition); + } + + @Override + public long getGroupId(int groupPosition) { + return groupPosition; + } + + @Override + public long getChildId(int groupPosition, int childPosition) { + return childPosition; + } + + @Override + public boolean hasStableIds() { + return false; + } + + @Override + public View getGroupView(final int groupPosition, final boolean isExpanded, View convertView, final ViewGroup parent) { + convertView = context.getLayoutInflater().inflate(R.layout.fragment_double_headed_value, parent, false); + + ImageView imageView = convertView.findViewById(R.id.imageView); + TextView firstHeadingView = convertView.findViewById(R.id.firstHeadingView); + TextView firstValueView = convertView.findViewById(R.id.firstValueView); + TextView secondHeadingView = convertView.findViewById(R.id.secondHeadingView); + TextView secondValueView = convertView.findViewById(R.id.secondValueView); + + imageView.setImageResource(R.drawable.cpu_icon); + firstHeadingView.setText(R.string.txtModel); + firstValueView.setText(cpuModelEntities.get(groupPosition).getModelName()); + secondHeadingView.setText(R.string.txtManufacturer); + secondValueView.setText(cpuModelEntities.get(groupPosition).getManufacturerName()); + + convertView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + ExpandableListView listView = (ExpandableListView) parent; + if (!isExpanded) { + listView.expandGroup(groupPosition); + } else { + listView.collapseGroup(groupPosition); + } + } + }); + + return convertView; + } + + @Override + public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) { + convertView = context.getLayoutInflater().inflate(R.layout.fragment_headed_value, parent, false); + + ImageView imageView = convertView.findViewById(R.id.imageView); + TextView headingView = convertView.findViewById(R.id.headingView); + TextView valueView = convertView.findViewById(R.id.valueView); + + switch (childPosition) { + case 0: + imageView.setImageResource(R.drawable.cpu_architecture_icon); + headingView.setText(R.string.txtArch); + valueView.setText(cpuModelEntities.get(groupPosition).getArchitectureName()); + break; + case 1: + imageView.setImageResource(R.drawable.cpu_clock_icon); + headingView.setText(R.string.txtClock); + valueView.setText(cpuModelEntities.get(groupPosition).getClockSpeedAsString()); + break; + case 2: + imageView.setImageResource(R.drawable.cpu_core_icon); + headingView.setText(R.string.txtCore); + valueView.setText(cpuModelEntities.get(groupPosition).getCoreCountAsString()); + break; + case 3: + imageView.setImageResource(R.drawable.cpu_thread_icon); + headingView.setText(R.string.txtThread); + valueView.setText(cpuModelEntities.get(groupPosition).getThreadCountAsString()); + break; + } + + return convertView; + } + + @Override + public boolean isChildSelectable(int groupPosition, int childPosition) { + return false; + } +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/adapter/expandablelistview/ProcessAdapter.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/adapter/expandablelistview/ProcessAdapter.java new file mode 100644 index 0000000..ffbe97a --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/adapter/expandablelistview/ProcessAdapter.java @@ -0,0 +1,146 @@ +package uk.co.syski.client.android.view.adapter.expandablelistview; + +import android.app.Activity; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseExpandableListAdapter; +import android.widget.ExpandableListView; +import android.widget.ImageButton; +import android.widget.ImageView; +import android.widget.TextView; + +import java.util.LinkedList; +import java.util.List; + +import uk.co.syski.client.android.R; +import uk.co.syski.client.android.model.database.entity.data.SystemProcessesEntity; +import uk.co.syski.client.android.view.activity.ProcessListActivity; + +public class ProcessAdapter extends BaseExpandableListAdapter { + + private ProcessListActivity context; + private List processModelList; + + public ProcessAdapter(ProcessListActivity context) { + this.context = context; + this.processModelList = new LinkedList<>(); + } + + public void setData(List processModelList) { + this.processModelList = processModelList; + notifyDataSetChanged(); + } + + @Override + public int getGroupCount() { + return processModelList.size(); + } + + @Override + public int getChildrenCount(int groupPosition) { + return 4; + } + + @Override + public Object getGroup(int groupPosition) { + return processModelList.get(groupPosition); + } + + @Override + public Object getChild(int groupPosition, int childPosition) { + return processModelList.get(groupPosition); + } + + @Override + public long getGroupId(int groupPosition) { + return groupPosition; + } + + @Override + public long getChildId(int groupPosition, int childPosition) { + return childPosition; + } + + @Override + public boolean hasStableIds() { + return false; + } + + @Override + public View getGroupView(final int groupPosition, final boolean isExpanded, View convertView, final ViewGroup parent) { + convertView = context.getLayoutInflater().inflate(R.layout.fragment_process, parent, false); + + ImageView imageView = convertView.findViewById(R.id.imageView); + TextView firstHeadingView = convertView.findViewById(R.id.firstHeadingView); + TextView firstValueView = convertView.findViewById(R.id.firstValueView); + TextView secondHeadingView = convertView.findViewById(R.id.secondHeadingView); + TextView secondValueView = convertView.findViewById(R.id.secondValueView); + + imageView.setImageResource(R.drawable.process_icon); + firstHeadingView.setText("Name"); + firstValueView.setText(processModelList.get(groupPosition).Name); + secondHeadingView.setText("Path"); + secondValueView.setText(processModelList.get(groupPosition).Path); + + convertView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + ExpandableListView listView = (ExpandableListView) parent; + if (!isExpanded) { + listView.expandGroup(groupPosition); + } else { + listView.collapseGroup(groupPosition); + } + } + }); + + ImageButton killButton = convertView.findViewById(R.id.killButton); + killButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + context.killProcess(processModelList.get(groupPosition)); + } + }); + + return convertView; + } + + @Override + public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) { + convertView = context.getLayoutInflater().inflate(R.layout.fragment_headed_value, parent, false); + + ImageView imageView = convertView.findViewById(R.id.imageView); + TextView headingView = convertView.findViewById(R.id.headingView); + TextView valueView = convertView.findViewById(R.id.valueView); + + switch (childPosition) { + case 0: + imageView.setImageResource(R.drawable.memory_size_icon); + headingView.setText("Memory Size"); + valueView.setText(String.valueOf(processModelList.get(groupPosition).MemSize)); + break; + case 1: + imageView.setImageResource(R.drawable.cpu_clock_icon); + headingView.setText("Kernel Time"); + valueView.setText(String.valueOf(processModelList.get(groupPosition).KernelTime)); + break; + case 2: + imageView.setImageResource(R.drawable.cpu_thread_icon); + headingView.setText("Threads"); + valueView.setText(String.valueOf(processModelList.get(groupPosition).Threads)); + break; + case 3: + imageView.setImageResource(R.drawable.cpu_clock_icon); + headingView.setText("Up Time"); + valueView.setText(String.valueOf(processModelList.get(groupPosition).UpTime)); + break; + } + + return convertView; + } + + @Override + public boolean isChildSelectable(int groupPosition, int childPosition) { + return false; + } +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/adapter/expandablelistview/RAMAdapter.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/adapter/expandablelistview/RAMAdapter.java new file mode 100644 index 0000000..a197b22 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/adapter/expandablelistview/RAMAdapter.java @@ -0,0 +1,121 @@ +package uk.co.syski.client.android.view.adapter.expandablelistview; + +import android.app.Activity; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseExpandableListAdapter; +import android.widget.ExpandableListView; +import android.widget.ImageView; +import android.widget.TextView; + +import java.util.LinkedList; +import java.util.List; + +import uk.co.syski.client.android.R; +import uk.co.syski.client.android.model.viewmodel.SystemRAMModel; + +public class RAMAdapter extends BaseExpandableListAdapter { + + private Activity context; + private List ramModelEntities; + + public RAMAdapter(Activity context) { + this.context = context; + this.ramModelEntities = new LinkedList<>(); + } + + public void setData(List ramModelEntities) { + this.ramModelEntities = ramModelEntities; + notifyDataSetChanged(); + } + + @Override + public int getGroupCount() { + return ramModelEntities.size(); + } + + @Override + public int getChildrenCount(int groupPosition) { + return 1; + } + + @Override + public Object getGroup(int groupPosition) { + return ramModelEntities.get(groupPosition); + } + + @Override + public Object getChild(int groupPosition, int childPosition) { + return ramModelEntities.get(groupPosition); + } + + @Override + public long getGroupId(int groupPosition) { + return groupPosition; + } + + @Override + public long getChildId(int groupPosition, int childPosition) { + return childPosition; + } + + @Override + public boolean hasStableIds() { + return false; + } + + @Override + public View getGroupView(final int groupPosition, final boolean isExpanded, View convertView, final ViewGroup parent) { + convertView = context.getLayoutInflater().inflate(R.layout.fragment_double_headed_value, parent, false); + + ImageView imageView = convertView.findViewById(R.id.imageView); + TextView firstHeadingView = convertView.findViewById(R.id.firstHeadingView); + TextView firstValueView = convertView.findViewById(R.id.firstValueView); + TextView secondHeadingView = convertView.findViewById(R.id.secondHeadingView); + TextView secondValueView = convertView.findViewById(R.id.secondValueView); + + imageView.setImageResource(R.drawable.ram_icon); + firstHeadingView.setText(R.string.txtModel); + firstValueView.setText(ramModelEntities.get(groupPosition).getModelName()); + secondHeadingView.setText(R.string.txtManufacturer); + secondValueView.setText(ramModelEntities.get(groupPosition).getManufacturerName()); + + convertView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + ExpandableListView listView = (ExpandableListView) parent; + if (!isExpanded) { + listView.expandGroup(groupPosition); + } else { + listView.collapseGroup(groupPosition); + } + } + }); + + return convertView; + } + + @Override + public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) { + convertView = context.getLayoutInflater().inflate(R.layout.fragment_headed_value, parent, false); + + ImageView imageView = convertView.findViewById(R.id.imageView); + TextView headingView = convertView.findViewById(R.id.headingView); + TextView valueView = convertView.findViewById(R.id.valueView); + + imageView.setImageResource(R.drawable.memory_size_icon); + headingView.setText(R.string.txtSize); + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); + valueView.setText(ramModelEntities.get(groupPosition).getMemoryBytesAsString(sp.getString("pref_general_ram_unit", context.getString(R.string.pref_general_ram_unit_default)))); + + return convertView; + } + + @Override + public boolean isChildSelectable(int groupPosition, int childPosition) { + return false; + } + +} \ No newline at end of file diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/adapter/expandablelistview/StorageAdapter.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/adapter/expandablelistview/StorageAdapter.java new file mode 100644 index 0000000..9fd4a3d --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/adapter/expandablelistview/StorageAdapter.java @@ -0,0 +1,121 @@ +package uk.co.syski.client.android.view.adapter.expandablelistview; + +import android.app.Activity; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseExpandableListAdapter; +import android.widget.ExpandableListView; +import android.widget.ImageView; +import android.widget.TextView; + +import java.util.LinkedList; +import java.util.List; + +import uk.co.syski.client.android.R; +import uk.co.syski.client.android.model.viewmodel.SystemStorageModel; + +public class StorageAdapter extends BaseExpandableListAdapter { + + private Activity context; + private List storageModelEntities; + + public StorageAdapter(Activity context) { + this.context = context; + this.storageModelEntities = new LinkedList<>(); + } + + public void setData(List storageModelEntities) { + this.storageModelEntities = storageModelEntities; + notifyDataSetChanged(); + } + + @Override + public int getGroupCount() { + return storageModelEntities.size(); + } + + @Override + public int getChildrenCount(int groupPosition) { + return 1; + } + + @Override + public Object getGroup(int groupPosition) { + return storageModelEntities.get(groupPosition); + } + + @Override + public Object getChild(int groupPosition, int childPosition) { + return storageModelEntities.get(groupPosition); + } + + @Override + public long getGroupId(int groupPosition) { + return groupPosition; + } + + @Override + public long getChildId(int groupPosition, int childPosition) { + return childPosition; + } + + @Override + public boolean hasStableIds() { + return false; + } + + @Override + public View getGroupView(final int groupPosition, final boolean isExpanded, View convertView, final ViewGroup parent) { + convertView = context.getLayoutInflater().inflate(R.layout.fragment_double_headed_value, parent, false); + + ImageView imageView = convertView.findViewById(R.id.imageView); + TextView firstHeadingView = convertView.findViewById(R.id.firstHeadingView); + TextView firstValueView = convertView.findViewById(R.id.firstValueView); + TextView secondHeadingView = convertView.findViewById(R.id.secondHeadingView); + TextView secondValueView = convertView.findViewById(R.id.secondValueView); + + imageView.setImageResource(R.drawable.storage_icon); + firstHeadingView.setText(R.string.txtModel); + firstValueView.setText(storageModelEntities.get(groupPosition).getModelName()); + secondHeadingView.setText(R.string.txtManufacturer); + secondValueView.setText(storageModelEntities.get(groupPosition).getManufacturerName()); + + convertView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + ExpandableListView listView = (ExpandableListView) parent; + if (!isExpanded) { + listView.expandGroup(groupPosition); + } else { + listView.collapseGroup(groupPosition); + } + } + }); + + return convertView; + } + + @Override + public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) { + convertView = context.getLayoutInflater().inflate(R.layout.fragment_headed_value, parent, false); + + ImageView imageView = convertView.findViewById(R.id.imageView); + TextView headingView = convertView.findViewById(R.id.headingView); + TextView valueView = convertView.findViewById(R.id.valueView); + + imageView.setImageResource(R.drawable.memory_size_icon); + headingView.setText(R.string.txtSize); + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); + valueView.setText(storageModelEntities.get(groupPosition).getMemoryBytesAsString(sp.getString("pref_general_storage_unit", context.getString(R.string.pref_general_storage_unit_default)))); + + return convertView; + } + + @Override + public boolean isChildSelectable(int groupPosition, int childPosition) { + return false; + } + +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/adapter/listview/DoubleHeadedValueListAdapter.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/adapter/listview/DoubleHeadedValueListAdapter.java new file mode 100644 index 0000000..2f4110c --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/adapter/listview/DoubleHeadedValueListAdapter.java @@ -0,0 +1,62 @@ +package uk.co.syski.client.android.view.adapter.listview; + +import android.app.Activity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.ImageView; +import android.widget.TextView; + +import java.util.List; + +import uk.co.syski.client.android.R; +import uk.co.syski.client.android.view.model.DoubleHeadedValueModel; + +public class DoubleHeadedValueListAdapter extends ArrayAdapter { + + private final Activity context; + private List listItems; + + ImageView imageView; + TextView firstHeadingView, firstValueView, secondHeadingView, secondValueView; + + View view; + + public DoubleHeadedValueListAdapter(Activity context) { + super(context, R.layout.fragment_overview); + this.context = context; + } + + public void setData(List listItems) + { + this.listItems = listItems; + clear(); + addAll(listItems); + notifyDataSetChanged(); + } + + @Override + public View getView(int position, View view, ViewGroup parent) { + initViews(); + + imageView.setImageResource(listItems.get(position).image); + firstHeadingView.setText(listItems.get(position).firstHeading); + firstValueView.setText(listItems.get(position).firstValue); + secondHeadingView.setText(listItems.get(position).secondHeading); + secondValueView.setText(listItems.get(position).secondValue); + + return this.view; + } + + public void initViews(){ + LayoutInflater inflater = context.getLayoutInflater(); + view = inflater.inflate(R.layout.fragment_double_headed_value, null, false); + imageView = view.findViewById(R.id.imageView); + firstHeadingView = view.findViewById(R.id.firstHeadingView); + firstValueView = view.findViewById(R.id.firstValueView); + secondHeadingView = view.findViewById(R.id.secondHeadingView); + secondValueView = view.findViewById(R.id.secondValueView); + + } +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/adapter/listview/HeadedValueListAdapter.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/adapter/listview/HeadedValueListAdapter.java new file mode 100644 index 0000000..ac3fb7f --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/adapter/listview/HeadedValueListAdapter.java @@ -0,0 +1,58 @@ +package uk.co.syski.client.android.view.adapter.listview; + +import android.app.Activity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.ImageView; +import android.widget.TextView; + +import java.util.List; + +import uk.co.syski.client.android.R; +import uk.co.syski.client.android.view.model.HeadedValueModel; + +public class HeadedValueListAdapter extends ArrayAdapter { + + private final Activity context; + private List listItems; + + ImageView image; + TextView heading, value; + View listView; + + public HeadedValueListAdapter(Activity context, List listItems) { + super(context, R.layout.fragment_overview, listItems); + this.context = context; + this.listItems = listItems; + } + + public void setData(List listItems) + { + this.listItems = listItems; + clear(); + addAll(listItems); + notifyDataSetChanged(); + } + + @Override + public View getView(int position, View view, ViewGroup parent) { + initViews(); + + image.setImageResource(listItems.get(position).image); + heading.setText(listItems.get(position).heading); + value.setText(listItems.get(position).value); + + return listView; + } + + public void initViews(){ + LayoutInflater inflater = context.getLayoutInflater(); + listView = inflater.inflate(R.layout.fragment_headed_value, null, true); + image = listView.findViewById(R.id.imageView); + heading = listView.findViewById(R.id.headingView); + value = listView.findViewById(R.id.valueView); + } + +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/adapter/recyclerview/SystemListAdapter.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/adapter/recyclerview/SystemListAdapter.java new file mode 100644 index 0000000..8a95382 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/adapter/recyclerview/SystemListAdapter.java @@ -0,0 +1,106 @@ +package uk.co.syski.client.android.view.adapter.recyclerview; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import java.util.LinkedList; +import java.util.List; + +import uk.co.syski.client.android.R; +import uk.co.syski.client.android.model.database.entity.SystemEntity; +import uk.co.syski.client.android.model.viewmodel.SystemModel; +import uk.co.syski.client.android.view.activity.SystemOverviewActivity; + +public class SystemListAdapter extends RecyclerView.Adapter { + + SharedPreferences prefs; + SharedPreferences.Editor prefEditor; + + List systemEntities; + + Activity context; + + public SystemListAdapter(Activity context) { + this.context = context; + this.systemEntities = new LinkedList<>(); + prefs = context.getSharedPreferences(context.getString(R.string.preference_sysID_key), Context.MODE_PRIVATE); + prefEditor = prefs.edit(); + } + + public void setData(List systemEntities) + { + this.systemEntities = systemEntities; + notifyDataSetChanged(); + } + + public static class SystemViewHolder extends RecyclerView.ViewHolder { + public View view; + + public SystemViewHolder(View itemView) { + super(itemView); + view = itemView; + } + } + + @Override + public SystemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.fragment_headed_value, parent, false); + SystemViewHolder systemViewHolder = new SystemViewHolder(view); + return systemViewHolder; + } + + @Override + public void onBindViewHolder(SystemViewHolder holder, final int position) { + ImageView imageView = holder.view.findViewById(R.id.imageView); + TextView headingView = holder.view.findViewById(R.id.headingView); + TextView valueView = holder.view.findViewById(R.id.valueView); + + if (systemEntities.get(position).getOnline()) + { + imageView.setImageResource(R.drawable.online_pc_icon); + } + else + { + imageView.setImageResource(R.drawable.offline_pc_icon); + } + + headingView.setText(R.string.txt_details_for); + valueView.setText(systemEntities.get(position).getHostNameAndPing()); + + holder.view.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + openSystemOverview(systemEntities.get(position).getId().toString()); + } + }); + } + + private void openSystemOverview(String systemId) { + Intent intent = new Intent(context,SystemOverviewActivity.class); + prefEditor.putString(context.getString(R.string.preference_sysID_key),systemId); + prefEditor.apply(); + context.startActivity(intent); + } + + @Override + public int getItemCount() { + return systemEntities.size(); + } + + public SystemModel getItem(int pos){ + return systemEntities.get(pos); + } + + public void removeItem(int pos){ + systemEntities.remove(pos); + notifyItemRemoved(pos); + } +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/fragment/DoubleHeadedValueFragment.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/fragment/DoubleHeadedValueFragment.java new file mode 100644 index 0000000..9e09a4c --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/fragment/DoubleHeadedValueFragment.java @@ -0,0 +1,62 @@ +package uk.co.syski.client.android.view.fragment; + + +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import uk.co.syski.client.android.R; +import uk.co.syski.client.android.view.model.DoubleHeadedValueModel; + +public class DoubleHeadedValueFragment extends Fragment { + // the fragment initialization parameters, e.g. ARG_ITEM_NUMBER + private DoubleHeadedValueModel model; + + private ImageView imageView; + private TextView firstHeadingView, firstValueView, secondHeadingView, secondValueView; + + + public DoubleHeadedValueFragment() { + // Required empty public constructor + } + + public static DoubleHeadedValueFragment newInstance(DoubleHeadedValueModel model) { + DoubleHeadedValueFragment fragment = new DoubleHeadedValueFragment(); + fragment.setModel(model); + return fragment; + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + // Inflate the layout for this fragment + return inflater.inflate(R.layout.fragment_double_headed_value, container, false); + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + imageView = view.findViewById(R.id.imageView); + firstHeadingView = view.findViewById(R.id.firstHeadingView); + firstValueView = view.findViewById(R.id.firstValueView); + secondHeadingView = view.findViewById(R.id.secondHeadingView); + secondValueView = view.findViewById(R.id.secondValueView); + + if (model.image != null) + imageView.setImageResource(model.image); + + firstHeadingView.setText(model.firstHeading); + firstValueView.setText(model.firstValue); + secondHeadingView.setText(model.secondHeading); + secondValueView.setText(model.secondValue); + } + + + private void setModel(DoubleHeadedValueModel model) { + this.model = model; + } + +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/fragment/HeadedValueFragment.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/fragment/HeadedValueFragment.java new file mode 100644 index 0000000..210b1db --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/fragment/HeadedValueFragment.java @@ -0,0 +1,54 @@ +package uk.co.syski.client.android.view.fragment; + + +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import uk.co.syski.client.android.R; +import uk.co.syski.client.android.view.model.HeadedValueModel; + +public class HeadedValueFragment extends Fragment { + + private HeadedValueModel model; + + private ImageView imageView; + private TextView headingView; + private TextView valueView; + + public HeadedValueFragment() {} + + public static HeadedValueFragment newInstance(HeadedValueModel model) { + HeadedValueFragment fragment = new HeadedValueFragment(); + fragment.setModel(model); + return fragment; + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + // Inflate the layout for this fragment + return inflater.inflate(R.layout.fragment_headed_value, container, false); + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + imageView = view.findViewById(R.id.imageView); + headingView = view.findViewById(R.id.headingView); + valueView = view.findViewById(R.id.valueView); + + if (model.image != null) + imageView.setImageResource(model.image); + + headingView.setText(model.heading); + valueView.setText(model.value); + } + + private void setModel(HeadedValueModel model) { + this.model = model; + } +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/fragment/OverviewFragment.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/fragment/OverviewFragment.java new file mode 100644 index 0000000..4ac0f64 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/fragment/OverviewFragment.java @@ -0,0 +1,65 @@ +package uk.co.syski.client.android.view.fragment; + +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ListView; + +import java.util.ArrayList; + +import uk.co.syski.client.android.R; +import uk.co.syski.client.android.view.adapter.listview.HeadedValueListAdapter; +import uk.co.syski.client.android.view.model.DoubleHeadedValueModel; +import uk.co.syski.client.android.view.model.HeadedValueModel; + + +/** + * A simple {@link Fragment} subclass. + * Use the {@link OverviewFragment#newInstance} factory method to + * create an instance of this fragment. + */ +public class OverviewFragment extends Fragment { + + private DoubleHeadedValueModel model; + private ArrayList listItems; + + private ListView listView; + + public OverviewFragment() { + // Required empty public constructor + } + + public static OverviewFragment newInstance(DoubleHeadedValueModel model, ArrayList listItems) { + OverviewFragment fragment = new OverviewFragment(); + fragment.setModel(model); + fragment.setListViewData(listItems); + return fragment; + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + // Inflate the layout for this fragment + return inflater.inflate(R.layout.fragment_overview, container, false); + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + DoubleHeadedValueFragment topFragment = DoubleHeadedValueFragment.newInstance(model); + getFragmentManager().beginTransaction().replace(R.id.topFragment, topFragment).commit(); + + listView = view.findViewById(R.id.listView); + if (listItems != null) + listView.setAdapter(new HeadedValueListAdapter(getActivity(), listItems)); + } + + private void setModel(DoubleHeadedValueModel model) { + this.model = model; + } + + private void setListViewData(ArrayList listItems) { + this.listItems = listItems; + } +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/graph/VariableCPULoadGraph.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/graph/VariableCPULoadGraph.java new file mode 100644 index 0000000..d516d4b --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/graph/VariableCPULoadGraph.java @@ -0,0 +1,73 @@ +package uk.co.syski.client.android.view.graph; + +import android.arch.lifecycle.Observer; +import android.arch.lifecycle.ViewModelProviders; +import android.support.annotation.Nullable; +import android.support.v7.app.AppCompatActivity; +import android.os.Bundle; + +import com.jjoe64.graphview.GraphView; +import com.jjoe64.graphview.series.DataPoint; +import com.jjoe64.graphview.series.LineGraphSeries; + +import java.util.List; + +import uk.co.syski.client.android.R; +import uk.co.syski.client.android.model.database.entity.data.CPUDataEntity; +import uk.co.syski.client.android.view.activity.SyskiActivity; +import uk.co.syski.client.android.view.menu.SyskiOptionsMenu; +import uk.co.syski.client.android.viewmodel.SystemCPUDataViewModel; + +public class VariableCPULoadGraph extends SyskiActivity { + + GraphView graph; + LineGraphSeries loadSeries; + private int mLastXValue = 0; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_graph); + + optionsMenu = new SyskiOptionsMenu(); + + graph = findViewById(R.id.graph); + + loadSeries = new LineGraphSeries(); + graph.addSeries(loadSeries); + + graph.getViewport().setYAxisBoundsManual(true); + graph.getViewport().setMinY(0); + graph.getViewport().setMaxY(100); + + graph.getViewport().setXAxisBoundsManual(true); + graph.getViewport().setMinX(0); + graph.getViewport().setMaxX(3); + graph.getGridLabelRenderer().setHumanRounding(true); + + graph.getGridLabelRenderer().setVerticalAxisTitle("CPU Load (%)"); + graph.getGridLabelRenderer().setHorizontalAxisTitle("Time (s)"); + + SystemCPUDataViewModel viewModel = ViewModelProviders.of(this).get(SystemCPUDataViewModel.class); + viewModel.get().observe(this, new Observer>() { + @Override + public void onChanged(@Nullable List cpuEntities) { + if (cpuEntities.size() > 0) + { + for (int i = 0; i < cpuEntities.size(); i++) { + CPUDataEntity current = cpuEntities.get(i); + loadSeries.appendData(new DataPoint(mLastXValue, current.Load), true, (mLastXValue / 3 + 1)); + mLastXValue += 3; + } + + graph.getViewport().setMinX(0); + graph.getViewport().setMaxX(mLastXValue - 3); + + graph.getGridLabelRenderer().setNumHorizontalLabels(6); + } + } + }); + + } + +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/graph/VariableCPUProcessesGraph.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/graph/VariableCPUProcessesGraph.java new file mode 100644 index 0000000..610a095 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/graph/VariableCPUProcessesGraph.java @@ -0,0 +1,72 @@ +package uk.co.syski.client.android.view.graph; + +import android.arch.lifecycle.Observer; +import android.arch.lifecycle.ViewModelProviders; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v7.app.AppCompatActivity; + +import com.jjoe64.graphview.GraphView; +import com.jjoe64.graphview.series.DataPoint; +import com.jjoe64.graphview.series.LineGraphSeries; + +import java.util.List; + +import uk.co.syski.client.android.R; +import uk.co.syski.client.android.model.database.entity.data.CPUDataEntity; +import uk.co.syski.client.android.view.activity.SyskiActivity; +import uk.co.syski.client.android.view.menu.SyskiOptionsMenu; +import uk.co.syski.client.android.viewmodel.SystemCPUDataViewModel; + +public class VariableCPUProcessesGraph extends SyskiActivity { + + GraphView graph; + LineGraphSeries loadSeries; + private int mLastXValue = 0; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_graph); + + optionsMenu = new SyskiOptionsMenu(); + + graph = findViewById(R.id.graph); + + loadSeries = new LineGraphSeries(); + graph.addSeries(loadSeries); + + graph.getViewport().setYAxisBoundsManual(true); + graph.getViewport().setMinY(0); + graph.getViewport().setMaxY(300); + + graph.getViewport().setXAxisBoundsManual(true); + graph.getViewport().setMinX(0); + graph.getViewport().setMaxX(3); + graph.getGridLabelRenderer().setHumanRounding(true); + + graph.getGridLabelRenderer().setVerticalAxisTitle("CPU Processes"); + graph.getGridLabelRenderer().setHorizontalAxisTitle("Time (s)"); + + SystemCPUDataViewModel viewModel = ViewModelProviders.of(this).get(SystemCPUDataViewModel.class); + viewModel.get().observe(this, new Observer>() { + @Override + public void onChanged(@Nullable List cpuEntities) { + if (cpuEntities.size() > 0) + { + for (int i = 0; i < cpuEntities.size(); i++) { + CPUDataEntity current = cpuEntities.get(i); + loadSeries.appendData(new DataPoint(mLastXValue, current.Processes), true, (mLastXValue / 3 + 1)); + mLastXValue += 3; + } + + graph.getViewport().setMinX(0); + graph.getViewport().setMaxX(mLastXValue - 3); + + graph.getGridLabelRenderer().setNumHorizontalLabels(6); + } + } + }); + } + +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/graph/VariableNetworkGraph.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/graph/VariableNetworkGraph.java new file mode 100644 index 0000000..f6090a0 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/graph/VariableNetworkGraph.java @@ -0,0 +1,25 @@ +package uk.co.syski.client.android.view.graph; + +import android.support.v7.app.AppCompatActivity; +import android.os.Bundle; + +import com.jjoe64.graphview.GraphView; + +import uk.co.syski.client.android.R; +import uk.co.syski.client.android.view.activity.SyskiActivity; +import uk.co.syski.client.android.view.menu.SyskiOptionsMenu; + +public class VariableNetworkGraph extends SyskiActivity { + + GraphView graph; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_graph); + + optionsMenu = new SyskiOptionsMenu(); + + graph = findViewById(R.id.graph); + } +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/graph/VariableRAMGraph.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/graph/VariableRAMGraph.java new file mode 100644 index 0000000..205bdc2 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/graph/VariableRAMGraph.java @@ -0,0 +1,89 @@ +package uk.co.syski.client.android.view.graph; + +import android.arch.lifecycle.Observer; +import android.arch.lifecycle.ViewModelProviders; +import android.support.annotation.Nullable; +import android.support.v7.app.AppCompatActivity; +import android.os.Bundle; + +import com.jjoe64.graphview.GraphView; +import com.jjoe64.graphview.series.DataPoint; +import com.jjoe64.graphview.series.LineGraphSeries; + +import java.util.List; + +import uk.co.syski.client.android.R; +import uk.co.syski.client.android.model.database.entity.data.RAMDataEntity; +import uk.co.syski.client.android.model.viewmodel.SystemRAMModel; +import uk.co.syski.client.android.view.activity.SyskiActivity; +import uk.co.syski.client.android.view.menu.SyskiOptionsMenu; +import uk.co.syski.client.android.viewmodel.SystemRAMDataViewModel; +import uk.co.syski.client.android.viewmodel.SystemRAMViewModel; + +public class VariableRAMGraph extends SyskiActivity { + + GraphView graph; + LineGraphSeries loadSeries; + private int mLastXValue = 0; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_graph); + + optionsMenu = new SyskiOptionsMenu(); + + graph = findViewById(R.id.graph); + + loadSeries = new LineGraphSeries(); + graph.addSeries(loadSeries); + + graph.getViewport().setYAxisBoundsManual(true); + graph.getViewport().setMinY(0); + + graph.getViewport().setXAxisBoundsManual(true); + graph.getViewport().setMinX(0); + graph.getViewport().setMaxX(3); + graph.getGridLabelRenderer().setHumanRounding(true); + + graph.getGridLabelRenderer().setVerticalAxisTitle("Free RAM (MB)"); + graph.getGridLabelRenderer().setHorizontalAxisTitle("Time (s)"); + + SystemRAMViewModel staticViewModel = ViewModelProviders.of(this).get(SystemRAMViewModel.class); + staticViewModel.get().observe(this, new Observer>() { + @Override + public void onChanged(@Nullable List ramEntities) { + if (ramEntities.size() > 0) + { + long totalRAM = 0; + for (int i = 0; i < ramEntities.size(); i++) { + totalRAM += ramEntities.get(i).getMemoryBytes(); + } + + graph.getViewport().setMaxY(totalRAM / (1024 * 1024)); + } + } + }); + + SystemRAMDataViewModel viewModel = ViewModelProviders.of(this).get(SystemRAMDataViewModel.class); + viewModel.get().observe(this, new Observer>() { + @Override + public void onChanged(@Nullable List ramEntities) { + if (ramEntities.size() > 0) + { + for (int i = 0; i < ramEntities.size(); i++) { + RAMDataEntity current = ramEntities.get(i); + loadSeries.appendData(new DataPoint(mLastXValue, current.Free), true, (mLastXValue / 3 + 1)); + mLastXValue += 3; + } + + graph.getViewport().setMinX(0); + graph.getViewport().setMaxX(mLastXValue - 3); + + graph.getGridLabelRenderer().setNumHorizontalLabels(6); + } + } + }); + + } +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/graph/VariableStorageByteReadWriteGraph.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/graph/VariableStorageByteReadWriteGraph.java new file mode 100644 index 0000000..313fde1 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/graph/VariableStorageByteReadWriteGraph.java @@ -0,0 +1,85 @@ +package uk.co.syski.client.android.view.graph; + +import android.arch.lifecycle.Observer; +import android.arch.lifecycle.ViewModelProviders; +import android.support.annotation.Nullable; +import android.support.v7.app.AppCompatActivity; +import android.os.Bundle; + +import com.jjoe64.graphview.GraphView; +import com.jjoe64.graphview.series.DataPoint; +import com.jjoe64.graphview.series.LineGraphSeries; + +import java.util.List; + +import uk.co.syski.client.android.R; +import uk.co.syski.client.android.model.database.entity.data.StorageDataEntity; +import uk.co.syski.client.android.view.activity.SyskiActivity; +import uk.co.syski.client.android.view.menu.SyskiOptionsMenu; +import uk.co.syski.client.android.viewmodel.SystemStorageDataViewModel; + +public class VariableStorageByteReadWriteGraph extends SyskiActivity { + + GraphView graph; + LineGraphSeries readSeries; + LineGraphSeries writeSeries; + private int mLastXValue = 0; + private float currentMaxY = 100; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_graph); + + optionsMenu = new SyskiOptionsMenu(); + + graph = findViewById(R.id.graph); + + readSeries = new LineGraphSeries(); + graph.addSeries(readSeries); + + writeSeries = new LineGraphSeries(); + graph.addSeries(writeSeries); + + graph.getViewport().setYAxisBoundsManual(true); + graph.getViewport().setMinY(0); + graph.getViewport().setMaxY(100); + + graph.getViewport().setXAxisBoundsManual(true); + graph.getViewport().setMinX(0); + graph.getViewport().setMaxX(3); + graph.getGridLabelRenderer().setHumanRounding(true); + + graph.getGridLabelRenderer().setVerticalAxisTitle("Storage Byte Reads and Writes"); + graph.getGridLabelRenderer().setHorizontalAxisTitle("Time (s)"); + + SystemStorageDataViewModel viewModel = ViewModelProviders.of(this).get(SystemStorageDataViewModel.class); + viewModel.get().observe(this, new Observer>() { + @Override + public void onChanged(@Nullable List storageEntities) { + if (storageEntities.size() > 0) + { + for (int i = 0; i < storageEntities.size(); i++) { + StorageDataEntity current = storageEntities.get(i); + if (current.ByteReads > currentMaxY) { + currentMaxY = current.ByteReads; + } else if (current.ByteWrites > currentMaxY) { + currentMaxY = current.ByteWrites; + } + readSeries.appendData(new DataPoint(mLastXValue, current.ByteReads), true, (mLastXValue / 3 + 1)); + writeSeries.appendData(new DataPoint(mLastXValue, current.ByteWrites), true, (mLastXValue / 3 + 1)); + mLastXValue += 3; + } + + graph.getViewport().setMinX(0); + graph.getViewport().setMaxX(mLastXValue - 3); + + graph.getViewport().setMaxY(currentMaxY); + + graph.getGridLabelRenderer().setNumHorizontalLabels(6); + } + } + }); + + } +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/graph/VariableStorageReadWriteGraph.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/graph/VariableStorageReadWriteGraph.java new file mode 100644 index 0000000..343a973 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/graph/VariableStorageReadWriteGraph.java @@ -0,0 +1,85 @@ +package uk.co.syski.client.android.view.graph; + +import android.arch.lifecycle.Observer; +import android.arch.lifecycle.ViewModelProviders; +import android.support.annotation.Nullable; +import android.support.v7.app.AppCompatActivity; +import android.os.Bundle; + +import com.jjoe64.graphview.GraphView; +import com.jjoe64.graphview.series.DataPoint; +import com.jjoe64.graphview.series.LineGraphSeries; + +import java.util.List; + +import uk.co.syski.client.android.R; +import uk.co.syski.client.android.model.database.entity.data.StorageDataEntity; +import uk.co.syski.client.android.view.activity.SyskiActivity; +import uk.co.syski.client.android.view.menu.SyskiOptionsMenu; +import uk.co.syski.client.android.viewmodel.SystemStorageDataViewModel; + +public class VariableStorageReadWriteGraph extends SyskiActivity { + + GraphView graph; + LineGraphSeries readSeries; + LineGraphSeries writeSeries; + private int mLastXValue = 0; + private float currentMaxY = 100; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_graph); + + optionsMenu = new SyskiOptionsMenu(); + + graph = findViewById(R.id.graph); + + readSeries = new LineGraphSeries(); + graph.addSeries(readSeries); + + writeSeries = new LineGraphSeries(); + graph.addSeries(writeSeries); + + graph.getViewport().setYAxisBoundsManual(true); + graph.getViewport().setMinY(0); + graph.getViewport().setMaxY(100); + + graph.getViewport().setXAxisBoundsManual(true); + graph.getViewport().setMinX(0); + graph.getViewport().setMaxX(3); + graph.getGridLabelRenderer().setHumanRounding(true); + + graph.getGridLabelRenderer().setVerticalAxisTitle("Storage Reads and Writes"); + graph.getGridLabelRenderer().setHorizontalAxisTitle("Time (s)"); + + SystemStorageDataViewModel viewModel = ViewModelProviders.of(this).get(SystemStorageDataViewModel.class); + viewModel.get().observe(this, new Observer>() { + @Override + public void onChanged(@Nullable List storageEntities) { + if (storageEntities.size() > 0) + { + for (int i = 0; i < storageEntities.size(); i++) { + StorageDataEntity current = storageEntities.get(i); + if (current.Reads > currentMaxY) { + currentMaxY = current.Reads; + } else if (current.Writes > currentMaxY) { + currentMaxY = current.Writes; + } + readSeries.appendData(new DataPoint(mLastXValue, current.Reads), true, (mLastXValue / 3 + 1)); + writeSeries.appendData(new DataPoint(mLastXValue, current.Writes), true, (mLastXValue / 3 + 1)); + mLastXValue += 3; + } + + graph.getViewport().setMinX(0); + graph.getViewport().setMaxX(mLastXValue - 3); + + graph.getViewport().setMaxY(currentMaxY); + + graph.getGridLabelRenderer().setNumHorizontalLabels(6); + } + } + }); + + } +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/graph/VariableStorageTimeGraph.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/graph/VariableStorageTimeGraph.java new file mode 100644 index 0000000..51d222f --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/graph/VariableStorageTimeGraph.java @@ -0,0 +1,78 @@ +package uk.co.syski.client.android.view.graph; + +import android.arch.lifecycle.Observer; +import android.arch.lifecycle.ViewModelProviders; +import android.support.annotation.Nullable; +import android.support.v7.app.AppCompatActivity; +import android.os.Bundle; + +import com.jjoe64.graphview.GraphView; +import com.jjoe64.graphview.series.DataPoint; +import com.jjoe64.graphview.series.LineGraphSeries; + +import java.util.List; + +import uk.co.syski.client.android.R; +import uk.co.syski.client.android.model.database.entity.data.StorageDataEntity; +import uk.co.syski.client.android.view.activity.SyskiActivity; +import uk.co.syski.client.android.view.menu.SyskiOptionsMenu; +import uk.co.syski.client.android.viewmodel.SystemStorageDataViewModel; + +public class VariableStorageTimeGraph extends SyskiActivity { + + GraphView graph; + LineGraphSeries loadSeries; + private int mLastXValue = 0; + private float currentMaxY = 100; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_graph); + + optionsMenu = new SyskiOptionsMenu(); + + graph = findViewById(R.id.graph); + + loadSeries = new LineGraphSeries(); + graph.addSeries(loadSeries); + + graph.getViewport().setYAxisBoundsManual(true); + graph.getViewport().setMinY(0); + graph.getViewport().setMaxY(100); + + graph.getViewport().setXAxisBoundsManual(true); + graph.getViewport().setMinX(0); + graph.getViewport().setMaxX(3); + graph.getGridLabelRenderer().setHumanRounding(true); + + graph.getGridLabelRenderer().setVerticalAxisTitle("Storage Time (s)"); + graph.getGridLabelRenderer().setHorizontalAxisTitle("Time (s)"); + + SystemStorageDataViewModel viewModel = ViewModelProviders.of(this).get(SystemStorageDataViewModel.class); + viewModel.get().observe(this, new Observer>() { + @Override + public void onChanged(@Nullable List storageEntities) { + if (storageEntities.size() > 0) + { + for (int i = 0; i < storageEntities.size(); i++) { + StorageDataEntity current = storageEntities.get(i); + if(current.Time > currentMaxY) { + currentMaxY = current.Time; + } + loadSeries.appendData(new DataPoint(mLastXValue, current.Time), true, (mLastXValue / 3 + 1)); + mLastXValue += 3; + } + + graph.getViewport().setMinX(0); + graph.getViewport().setMaxX(mLastXValue - 3); + + graph.getViewport().setMaxY(currentMaxY); + + graph.getGridLabelRenderer().setNumHorizontalLabels(6); + } + } + }); + + } +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/graph/VariableStorageTransfersGraph.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/graph/VariableStorageTransfersGraph.java new file mode 100644 index 0000000..ed15a0d --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/graph/VariableStorageTransfersGraph.java @@ -0,0 +1,78 @@ +package uk.co.syski.client.android.view.graph; + +import android.arch.lifecycle.Observer; +import android.arch.lifecycle.ViewModelProviders; +import android.support.annotation.Nullable; +import android.support.v7.app.AppCompatActivity; +import android.os.Bundle; + +import com.jjoe64.graphview.GraphView; +import com.jjoe64.graphview.series.DataPoint; +import com.jjoe64.graphview.series.LineGraphSeries; + +import java.util.List; + +import uk.co.syski.client.android.R; +import uk.co.syski.client.android.model.database.entity.data.StorageDataEntity; +import uk.co.syski.client.android.view.activity.SyskiActivity; +import uk.co.syski.client.android.view.menu.SyskiOptionsMenu; +import uk.co.syski.client.android.viewmodel.SystemStorageDataViewModel; + +public class VariableStorageTransfersGraph extends SyskiActivity { + + GraphView graph; + LineGraphSeries loadSeries; + private int mLastXValue = 0; + private float currentMaxY = 100; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_graph); + + optionsMenu = new SyskiOptionsMenu(); + + graph = findViewById(R.id.graph); + + loadSeries = new LineGraphSeries(); + graph.addSeries(loadSeries); + + graph.getViewport().setYAxisBoundsManual(true); + graph.getViewport().setMinY(0); + graph.getViewport().setMaxY(100); + + graph.getViewport().setXAxisBoundsManual(true); + graph.getViewport().setMinX(0); + graph.getViewport().setMaxX(3); + graph.getGridLabelRenderer().setHumanRounding(true); + + graph.getGridLabelRenderer().setVerticalAxisTitle("Storage Transfers"); + graph.getGridLabelRenderer().setHorizontalAxisTitle("Time (s)"); + + SystemStorageDataViewModel viewModel = ViewModelProviders.of(this).get(SystemStorageDataViewModel.class); + viewModel.get().observe(this, new Observer>() { + @Override + public void onChanged(@Nullable List storageEntities) { + if (storageEntities.size() > 0) + { + for (int i = 0; i < storageEntities.size(); i++) { + StorageDataEntity current = storageEntities.get(i); + if(current.Time > currentMaxY) { + currentMaxY = current.Time; + } + loadSeries.appendData(new DataPoint(mLastXValue, current.Transfers), true, (mLastXValue / 3 + 1)); + mLastXValue += 3; + } + + graph.getViewport().setMinX(0); + graph.getViewport().setMaxX(mLastXValue - 3); + + graph.getViewport().setMaxY(currentMaxY); + + graph.getGridLabelRenderer().setNumHorizontalLabels(6); + } + } + }); + + } +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/menu/ActivityOptionsMenu.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/menu/ActivityOptionsMenu.java new file mode 100644 index 0000000..faf90e3 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/menu/ActivityOptionsMenu.java @@ -0,0 +1,12 @@ +package uk.co.syski.client.android.view.menu; + +import android.app.Activity; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; + +public interface ActivityOptionsMenu { + public boolean create(Menu menu, MenuInflater inflater); + + public boolean onItemSelected(MenuItem item, Activity context); +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/menu/SyskiOptionsMenu.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/menu/SyskiOptionsMenu.java new file mode 100644 index 0000000..be3e68f --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/menu/SyskiOptionsMenu.java @@ -0,0 +1,40 @@ +package uk.co.syski.client.android.view.menu; + +import android.app.Activity; +import android.content.Intent; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; + +import uk.co.syski.client.android.R; +import uk.co.syski.client.android.view.activity.SettingsActivity; +import uk.co.syski.client.android.view.activity.SystemListMenu; + +public class SyskiOptionsMenu implements ActivityOptionsMenu { + + @Override + public boolean create(Menu menu, MenuInflater inflater) { + inflater.inflate(R.menu.appbar, menu); + return true; + } + + @Override + public boolean onItemSelected(MenuItem item, Activity context) { + int id = item.getItemId(); + + Intent settings = null; + switch (id) { + case R.id.action_syslist: + settings = new Intent(context, SystemListMenu.class); + break; + case R.id.action_settings: + settings = new Intent(context, SettingsActivity.class); + break; + } + + if (settings != null) + context.startActivity(settings); + + return settings != null; + } +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/menu/SystemListOptionsMenu.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/menu/SystemListOptionsMenu.java new file mode 100644 index 0000000..27c8f0f --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/menu/SystemListOptionsMenu.java @@ -0,0 +1,31 @@ +package uk.co.syski.client.android.view.menu; + +import android.app.Activity; +import android.content.Intent; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; + +import uk.co.syski.client.android.R; +import uk.co.syski.client.android.view.activity.SettingsActivity; + +public class SystemListOptionsMenu implements ActivityOptionsMenu { + @Override + public boolean create(Menu menu, MenuInflater inflater) { + inflater.inflate(R.menu.sys_list_menu, menu); + return true; + } + + @Override + public boolean onItemSelected(MenuItem item, Activity context) { + int id = item.getItemId(); + + if (id == R.id.action_settings) { + Intent settings = new Intent(context, SettingsActivity.class); + context.startActivity(settings); + return true; + } + + return false; + } +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/model/DoubleHeadedValueModel.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/model/DoubleHeadedValueModel.java new file mode 100644 index 0000000..73f5ad7 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/model/DoubleHeadedValueModel.java @@ -0,0 +1,17 @@ +package uk.co.syski.client.android.view.model; + +public class DoubleHeadedValueModel { + public final Integer image; + public final String firstHeading; + public final String firstValue; + public final String secondHeading; + public final String secondValue; + + public DoubleHeadedValueModel(Integer imageIn, String firstHeadingIn, String firstValueIn, String secondHeadingIn, String secondValueIn) { + image = imageIn; + firstHeading = firstHeadingIn; + firstValue = firstValueIn; + secondHeading = secondHeadingIn; + secondValue = secondValueIn; + } +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/model/HeadedValueModel.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/model/HeadedValueModel.java new file mode 100644 index 0000000..7dc3494 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/view/model/HeadedValueModel.java @@ -0,0 +1,13 @@ +package uk.co.syski.client.android.view.model; + +public class HeadedValueModel { + public final Integer image; + public final String heading; + public final String value; + + public HeadedValueModel(Integer imageIn, String headingIn, String valueIn) { + image = imageIn; + heading = headingIn; + value = valueIn; + } +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/viewmodel/MotherboardViewModel.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/viewmodel/MotherboardViewModel.java new file mode 100644 index 0000000..8744e7f --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/viewmodel/MotherboardViewModel.java @@ -0,0 +1,33 @@ +package uk.co.syski.client.android.viewmodel; + +import android.app.Application; +import android.arch.lifecycle.AndroidViewModel; +import android.arch.lifecycle.LiveData; +import android.content.Context; +import android.support.annotation.NonNull; + +import java.util.UUID; + +import uk.co.syski.client.android.R; +import uk.co.syski.client.android.model.repository.MOBORepository; +import uk.co.syski.client.android.model.repository.Repository; +import uk.co.syski.client.android.model.viewmodel.SystemMotherboardModel; + +public class MotherboardViewModel extends AndroidViewModel{ + + private MOBORepository mMOBORepository; + private LiveData mSystemMOBO; + private UUID systemId; + + public MotherboardViewModel(@NonNull Application application) { + super(application); + systemId = UUID.fromString(application.getSharedPreferences(application.getString(R.string.preference_sysID_key), Context.MODE_PRIVATE).getString(application.getString(R.string.preference_sysID_key), null)); + mMOBORepository = Repository.getInstance().getMOBORepository(); + mSystemMOBO = mMOBORepository.getSystemMotherboardLiveData(systemId, getApplication().getBaseContext()); + } + + public LiveData get() { + return mSystemMOBO; + } + +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/viewmodel/OperatingSystemViewModel.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/viewmodel/OperatingSystemViewModel.java new file mode 100644 index 0000000..2ad76cf --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/viewmodel/OperatingSystemViewModel.java @@ -0,0 +1,34 @@ +package uk.co.syski.client.android.viewmodel; + +import android.app.Application; +import android.arch.lifecycle.AndroidViewModel; +import android.arch.lifecycle.LiveData; +import android.content.Context; +import android.support.annotation.NonNull; + +import java.util.List; +import java.util.UUID; + +import uk.co.syski.client.android.R; +import uk.co.syski.client.android.model.repository.OSRepository; +import uk.co.syski.client.android.model.repository.Repository; +import uk.co.syski.client.android.model.viewmodel.OperatingSystemModel; + +public class OperatingSystemViewModel extends AndroidViewModel{ + + private OSRepository mOSRepository; + private LiveData> mSystemOSList; + private UUID systemId; + + public OperatingSystemViewModel(@NonNull Application application) { + super(application); + systemId = UUID.fromString(application.getSharedPreferences(application.getString(R.string.preference_sysID_key), Context.MODE_PRIVATE).getString(application.getString(R.string.preference_sysID_key), null)); + mOSRepository = Repository.getInstance().getOSRepository(); + mSystemOSList = mOSRepository.getSystemOSsLiveData(systemId, application.getBaseContext()); + } + + public LiveData> get() { + return mSystemOSList; + } + +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/viewmodel/SystemBIOSViewModel.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/viewmodel/SystemBIOSViewModel.java new file mode 100644 index 0000000..bb196cf --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/viewmodel/SystemBIOSViewModel.java @@ -0,0 +1,33 @@ +package uk.co.syski.client.android.viewmodel; + +import android.app.Application; +import android.arch.lifecycle.AndroidViewModel; +import android.arch.lifecycle.LiveData; +import android.content.Context; +import android.support.annotation.NonNull; + +import java.util.UUID; + +import uk.co.syski.client.android.R; +import uk.co.syski.client.android.model.repository.BIOSRepository; +import uk.co.syski.client.android.model.repository.Repository; +import uk.co.syski.client.android.model.viewmodel.SystemBIOSModel; + +public class SystemBIOSViewModel extends AndroidViewModel { + + private BIOSRepository mBIOSRepository; + private LiveData mSystemBIOS; + private UUID systemId; + + public SystemBIOSViewModel(@NonNull Application application) { + super(application); + systemId = UUID.fromString(application.getSharedPreferences(application.getString(R.string.preference_sysID_key), Context.MODE_PRIVATE).getString(application.getString(R.string.preference_sysID_key), null)); + mBIOSRepository = Repository.getInstance().getBIOSRepository(); + mSystemBIOS = mBIOSRepository.getSystemBIOSLiveData(systemId, getApplication().getBaseContext()); + } + + public LiveData get() { + return mSystemBIOS; + } + +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/viewmodel/SystemCPUDataViewModel.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/viewmodel/SystemCPUDataViewModel.java new file mode 100644 index 0000000..01d92b7 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/viewmodel/SystemCPUDataViewModel.java @@ -0,0 +1,41 @@ +package uk.co.syski.client.android.viewmodel; + +import android.app.Application; +import android.arch.lifecycle.AndroidViewModel; +import android.arch.lifecycle.MutableLiveData; +import android.content.Context; +import android.support.annotation.NonNull; + +import java.util.List; +import java.util.UUID; + +import uk.co.syski.client.android.R; +import uk.co.syski.client.android.model.database.entity.data.CPUDataEntity; +import uk.co.syski.client.android.model.repository.SystemCPUDataRepository; + +public class SystemCPUDataViewModel extends AndroidViewModel { + + private SystemCPUDataRepository mCPUDataRepository; + private MutableLiveData> mSystemDataCPUList; + private UUID systemId; + + public SystemCPUDataViewModel(@NonNull Application application) { + super(application); + systemId = UUID.fromString(application.getSharedPreferences(application.getString(R.string.preference_sysID_key), Context.MODE_PRIVATE).getString(application.getString(R.string.preference_sysID_key), null)); + mCPUDataRepository = new SystemCPUDataRepository(application, systemId); + mSystemDataCPUList = mCPUDataRepository.get(); + mCPUDataRepository.start(); + } + + public MutableLiveData> get() { + return mSystemDataCPUList; + } + + @Override + public void onCleared() + { + mCPUDataRepository.stop(); + } + + +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/viewmodel/SystemCPUViewModel.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/viewmodel/SystemCPUViewModel.java new file mode 100644 index 0000000..2d467ad --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/viewmodel/SystemCPUViewModel.java @@ -0,0 +1,35 @@ +package uk.co.syski.client.android.viewmodel; + +import android.app.Application; +import android.arch.lifecycle.AndroidViewModel; +import android.arch.lifecycle.LiveData; +import android.content.Context; +import android.support.annotation.NonNull; + +import java.util.List; +import java.util.UUID; + +import uk.co.syski.client.android.R; +import uk.co.syski.client.android.model.viewmodel.SystemCPUModel; +import uk.co.syski.client.android.model.repository.CPURepository; +import uk.co.syski.client.android.model.repository.Repository; + +public class SystemCPUViewModel extends AndroidViewModel { + + private CPURepository mCPURepository; + + private UUID systemId; + private LiveData> mSystemCPUList; + + public SystemCPUViewModel(@NonNull Application application) { + super(application); + systemId = UUID.fromString(application.getSharedPreferences(application.getString(R.string.preference_sysID_key), Context.MODE_PRIVATE).getString(application.getString(R.string.preference_sysID_key), null)); + mCPURepository = Repository.getInstance().getCPURepository(); + mSystemCPUList = mCPURepository.getSystemCPUsLiveData(systemId, application.getBaseContext()); + } + + public LiveData> get() { + return mSystemCPUList; + } + +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/viewmodel/SystemGPUViewModel.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/viewmodel/SystemGPUViewModel.java new file mode 100644 index 0000000..8e9449f --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/viewmodel/SystemGPUViewModel.java @@ -0,0 +1,32 @@ +package uk.co.syski.client.android.viewmodel; + +import android.app.Application; +import android.arch.lifecycle.AndroidViewModel; +import android.arch.lifecycle.LiveData; +import android.content.Context; +import android.support.annotation.NonNull; + +import java.util.List; +import java.util.UUID; + +import uk.co.syski.client.android.R; +import uk.co.syski.client.android.model.viewmodel.SystemGPUModel; +import uk.co.syski.client.android.model.repository.GPURepository; +import uk.co.syski.client.android.model.repository.Repository; + +public class SystemGPUViewModel extends AndroidViewModel { + + private GPURepository mGPURepository; + private LiveData> mSystemGPUList; + private UUID systemId; + + public SystemGPUViewModel(@NonNull Application application) { + super(application); + systemId = UUID.fromString(application.getSharedPreferences(application.getString(R.string.preference_sysID_key), Context.MODE_PRIVATE).getString(application.getString(R.string.preference_sysID_key), null)); + mGPURepository = Repository.getInstance().getGPURepository(); + mSystemGPUList = mGPURepository.getSystemGPUsLiveData(systemId, application.getBaseContext()); + } + + public LiveData> get() { return mSystemGPUList; } + +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/viewmodel/SystemListViewModel.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/viewmodel/SystemListViewModel.java new file mode 100644 index 0000000..05fc05e --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/viewmodel/SystemListViewModel.java @@ -0,0 +1,42 @@ +package uk.co.syski.client.android.viewmodel; + +import android.app.Application; +import android.arch.lifecycle.AndroidViewModel; +import android.arch.lifecycle.LiveData; +import android.support.annotation.NonNull; + +import java.util.List; +import java.util.UUID; + +import uk.co.syski.client.android.model.database.entity.SystemEntity; +import uk.co.syski.client.android.model.repository.Repository; +import uk.co.syski.client.android.model.repository.SystemRepository; +import uk.co.syski.client.android.model.viewmodel.SystemModel; + +public class SystemListViewModel extends AndroidViewModel { + + private SystemRepository mSystemRepository; + private LiveData> mSystemList; + + public SystemListViewModel(@NonNull Application application) { + super(application); + mSystemRepository = Repository.getInstance().getSystemRepository(); + mSystemList = mSystemRepository.getSystemsLiveData(application); + mSystemRepository.start(application); + } + + public LiveData> get() { + return mSystemList; + } + + public void delete(UUID id){ + mSystemRepository.delete(id, this.getApplication().getBaseContext()); + } + + @Override + public void onCleared() + { + mSystemRepository.stop(); + } + +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/viewmodel/SystemProcessesViewModel.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/viewmodel/SystemProcessesViewModel.java new file mode 100644 index 0000000..ae0b077 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/viewmodel/SystemProcessesViewModel.java @@ -0,0 +1,50 @@ +package uk.co.syski.client.android.viewmodel; + +import android.app.Application; +import android.arch.lifecycle.AndroidViewModel; +import android.arch.lifecycle.MutableLiveData; +import android.content.Context; +import android.support.annotation.NonNull; + +import java.util.List; +import java.util.UUID; + +import uk.co.syski.client.android.R; +import uk.co.syski.client.android.model.api.VolleySingleton; +import uk.co.syski.client.android.model.api.requests.system.APISystemKillProcess; +import uk.co.syski.client.android.model.api.requests.system.APISystemProcessDataRequest; +import uk.co.syski.client.android.model.database.entity.data.SystemProcessesEntity; +import uk.co.syski.client.android.model.repository.SystemProcessesRepository; +import uk.co.syski.client.android.model.repository.SystemStorageDataRepository; + +public class SystemProcessesViewModel extends AndroidViewModel +{ + + private SystemProcessesRepository mProcessesRepository; + private MutableLiveData> mSystemDataProcessesList; + private UUID systemId; + + public SystemProcessesViewModel(@NonNull Application application) { + super(application); + systemId = UUID.fromString(application.getSharedPreferences(application.getString(R.string.preference_sysID_key), Context.MODE_PRIVATE).getString(application.getString(R.string.preference_sysID_key), null)); + mProcessesRepository = new SystemProcessesRepository(application, systemId); + mSystemDataProcessesList = mProcessesRepository.get(); + mProcessesRepository.start(); + } + + public MutableLiveData> get() { + return mSystemDataProcessesList; + } + + public void killProcess(int id) + { + VolleySingleton.getInstance(getApplication().getBaseContext()).addToRequestQueue(new APISystemKillProcess(getApplication().getBaseContext(), systemId, id)); + } + + @Override + public void onCleared() + { + mProcessesRepository.stop(); + } + +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/viewmodel/SystemRAMDataViewModel.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/viewmodel/SystemRAMDataViewModel.java new file mode 100644 index 0000000..6bf20a2 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/viewmodel/SystemRAMDataViewModel.java @@ -0,0 +1,41 @@ +package uk.co.syski.client.android.viewmodel; + +import android.app.Application; +import android.arch.lifecycle.AndroidViewModel; +import android.arch.lifecycle.MutableLiveData; +import android.content.Context; +import android.support.annotation.NonNull; + +import java.util.List; +import java.util.UUID; + +import uk.co.syski.client.android.R; +import uk.co.syski.client.android.model.database.entity.data.RAMDataEntity; +import uk.co.syski.client.android.model.repository.SystemRAMDataRepository; + +public class SystemRAMDataViewModel extends AndroidViewModel { + + private SystemRAMDataRepository mRAMDataRepository; + private MutableLiveData> mSystemDataRAMList; + private UUID systemId; + + public SystemRAMDataViewModel(@NonNull Application application) { + super(application); + systemId = UUID.fromString(application.getSharedPreferences(application.getString(R.string.preference_sysID_key), Context.MODE_PRIVATE).getString(application.getString(R.string.preference_sysID_key), null)); + mRAMDataRepository = new SystemRAMDataRepository(application, systemId); + mSystemDataRAMList = mRAMDataRepository.get(); + mRAMDataRepository.start(); + } + + public MutableLiveData> get() { + return mSystemDataRAMList; + } + + @Override + public void onCleared() + { + mRAMDataRepository.stop(); + } + + +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/viewmodel/SystemRAMViewModel.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/viewmodel/SystemRAMViewModel.java new file mode 100644 index 0000000..d2e6799 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/viewmodel/SystemRAMViewModel.java @@ -0,0 +1,34 @@ +package uk.co.syski.client.android.viewmodel; + +import android.app.Application; +import android.arch.lifecycle.AndroidViewModel; +import android.arch.lifecycle.LiveData; +import android.content.Context; +import android.support.annotation.NonNull; + +import java.util.List; +import java.util.UUID; + +import uk.co.syski.client.android.R; +import uk.co.syski.client.android.model.viewmodel.SystemRAMModel; +import uk.co.syski.client.android.model.repository.RAMRepository; +import uk.co.syski.client.android.model.repository.Repository; + +public class SystemRAMViewModel extends AndroidViewModel { + + private RAMRepository mRAMRepository; + private LiveData> mRAMList; + private UUID systemId; + + public SystemRAMViewModel(@NonNull Application application) { + super(application); + systemId = UUID.fromString(application.getSharedPreferences(application.getString(R.string.preference_sysID_key), Context.MODE_PRIVATE).getString(application.getString(R.string.preference_sysID_key), null)); + mRAMRepository = Repository.getInstance().getRAMRepository(); + mRAMList = mRAMRepository.getSystemRAMsLiveData(systemId, application.getBaseContext()); + } + + public LiveData> get() { + return mRAMList; + } + +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/viewmodel/SystemStorageDataViewModel.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/viewmodel/SystemStorageDataViewModel.java new file mode 100644 index 0000000..628ddaf --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/viewmodel/SystemStorageDataViewModel.java @@ -0,0 +1,39 @@ +package uk.co.syski.client.android.viewmodel; + +import android.app.Application; +import android.arch.lifecycle.AndroidViewModel; +import android.arch.lifecycle.MutableLiveData; +import android.content.Context; +import android.support.annotation.NonNull; + +import java.util.List; +import java.util.UUID; + +import uk.co.syski.client.android.R; +import uk.co.syski.client.android.model.database.entity.data.StorageDataEntity; +import uk.co.syski.client.android.model.repository.SystemStorageDataRepository; + +public class SystemStorageDataViewModel extends AndroidViewModel { + + private SystemStorageDataRepository mStorageDataRepository; + private MutableLiveData> mSystemDataStorageList; + private UUID systemId; + + public SystemStorageDataViewModel(@NonNull Application application) { + super(application); + systemId = UUID.fromString(application.getSharedPreferences(application.getString(R.string.preference_sysID_key), Context.MODE_PRIVATE).getString(application.getString(R.string.preference_sysID_key), null)); + mStorageDataRepository = new SystemStorageDataRepository(application, systemId); + mSystemDataStorageList = mStorageDataRepository.get(); + mStorageDataRepository.start(); + } + + public MutableLiveData> get() { + return mSystemDataStorageList; + } + + @Override + public void onCleared() + { + mStorageDataRepository.stop(); + } +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/viewmodel/SystemStorageViewModel.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/viewmodel/SystemStorageViewModel.java new file mode 100644 index 0000000..1c12805 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/viewmodel/SystemStorageViewModel.java @@ -0,0 +1,34 @@ +package uk.co.syski.client.android.viewmodel; + +import android.app.Application; +import android.arch.lifecycle.AndroidViewModel; +import android.arch.lifecycle.LiveData; +import android.content.Context; +import android.support.annotation.NonNull; + +import java.util.List; +import java.util.UUID; + +import uk.co.syski.client.android.R; +import uk.co.syski.client.android.model.repository.Repository; +import uk.co.syski.client.android.model.repository.StorageRepository; +import uk.co.syski.client.android.model.viewmodel.SystemStorageModel; + +public class SystemStorageViewModel extends AndroidViewModel { + + private StorageRepository mStorageRepository; + private LiveData> mSystemStorageList; + private UUID systemId; + + public SystemStorageViewModel(@NonNull Application application) { + super(application); + systemId = UUID.fromString(application.getSharedPreferences(application.getString(R.string.preference_sysID_key), Context.MODE_PRIVATE).getString(application.getString(R.string.preference_sysID_key), null)); + mStorageRepository = Repository.getInstance().getStorageRepository(); + mSystemStorageList = mStorageRepository.getSystemStoragesLiveData(systemId, application.getBaseContext()); + } + + public LiveData> get() { + return mSystemStorageList; + } + +} diff --git a/syski_client_android/app/src/main/java/uk/co/syski/client/android/viewmodel/SystemSummaryViewModel.java b/syski_client_android/app/src/main/java/uk/co/syski/client/android/viewmodel/SystemSummaryViewModel.java new file mode 100644 index 0000000..ea4e521 --- /dev/null +++ b/syski_client_android/app/src/main/java/uk/co/syski/client/android/viewmodel/SystemSummaryViewModel.java @@ -0,0 +1,64 @@ +package uk.co.syski.client.android.viewmodel; + +import android.app.Application; +import android.arch.lifecycle.AndroidViewModel; +import android.arch.lifecycle.LiveData; +import android.content.Context; +import android.support.annotation.NonNull; + +import java.util.Calendar; +import java.util.UUID; + +import uk.co.syski.client.android.R; +import uk.co.syski.client.android.model.api.VolleySingleton; +import uk.co.syski.client.android.model.api.requests.auth.APITokenRequest; +import uk.co.syski.client.android.model.api.requests.system.APISystemRestartRequest; +import uk.co.syski.client.android.model.api.requests.system.APISystemShutdownRequest; +import uk.co.syski.client.android.model.database.entity.SystemEntity; +import uk.co.syski.client.android.model.database.entity.UserEntity; +import uk.co.syski.client.android.model.repository.Repository; +import uk.co.syski.client.android.model.repository.SystemRepository; +import uk.co.syski.client.android.model.viewmodel.SystemModel; + +public class SystemSummaryViewModel extends AndroidViewModel { + + private String TAG = this.getClass().getSimpleName(); + + private SystemRepository mSystemRepository; + private LiveData mSystem; + private UUID systemId; + + public SystemSummaryViewModel(@NonNull Application application) { + super(application); + systemId = UUID.fromString(application.getSharedPreferences(application.getString(R.string.preference_sysID_key), Context.MODE_PRIVATE).getString(application.getString(R.string.preference_sysID_key), null)); + mSystemRepository = Repository.getInstance().getSystemRepository(); + mSystem = mSystemRepository.getSystemLiveData(systemId, application.getBaseContext()); + } + + public void shutdownOnClick() { + UserEntity user = null; + user = Repository.getInstance().getUserRepository().getUser(); + if (user == null || user.TokenExpiry == null || Calendar.getInstance().getTime().after(user.TokenExpiry)) + { + VolleySingleton.getInstance(getApplication()).addToRequestQueue(new APITokenRequest(getApplication(), user.Id)); + } + VolleySingleton.getInstance(this.getApplication()).addToRequestQueue(new APISystemShutdownRequest(getApplication(), systemId)); + } + + public void restartOnClick() { + UserEntity user = null; + user = Repository.getInstance().getUserRepository().getUser(); + if (user == null || user.TokenExpiry == null || Calendar.getInstance().getTime().after(user.TokenExpiry)) + { + VolleySingleton.getInstance(getApplication()).addToRequestQueue(new APITokenRequest(getApplication(), user.Id)); + } + VolleySingleton.getInstance(getApplication()).addToRequestQueue(new APISystemRestartRequest(getApplication(), systemId)); + } + + public LiveData get() + { + return mSystem; + } + + +} diff --git a/syski_client_android/app/src/main/res/drawable-mdpi/syski_sys_icon.png b/syski_client_android/app/src/main/res/drawable-mdpi/syski_sys_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..c0ec0c199e0145f9ae5b22edd2f7bd77b1eabc81 GIT binary patch literal 30316 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�V@&mw;}#Vq9O@)oY&dBMiUR^;|bt@1H) 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 z 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 + + + + + + + + + + diff --git a/syski_client_android/app/src/main/res/drawable/bios_icon.png b/syski_client_android/app/src/main/res/drawable/bios_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..4d0d9e815cdfb4df776bed7e6e35fe150dedd669 GIT binary patch literal 1763 zcmZ8gc{JOJ8vR)!Z6X>jWusI?TO_AUBQN|zzXjF-NdKi->j&;9Oq?z!hX-#<6SfkHySj=%r_K#*++ zjw1E`$^%lO8MKgh0RSYBoC{R8qemDR#0vCb_|ri4wICWu3upKMK={P^?ei>UZRx~W zGh#-xdVG5@onE;mbzLYlU|(NT&-kt&>4svtn02{E@uA<}&I`Bi%eE8;mnmo8{de|N zf}Ms!H8nh<;Wz74#?7CX8n&04EvL{1Xh^+f3uorjVOHX)z-yDCC-}SGliX6}W>lcq`9%mEVpQq8FnI}b2X@*M}HTOK5t(p~!N}^M5>(tzT zJb*dmyo}zlDBDhrRExqKb;8#mcFr81{d{hzEb!B2Iy?A!OT^BWkiuj&KIu+?&niy8 zG*x~0v-V=wHPuz~9#`DPkK?ZAXzUlH(gE**D}zThUI*PW*Bg(SDMv~y77+?hAk4?6 z%GKK2wc?IpO;CPfcxd|bfO$&&u)BmnPi7D?h69%@P_E2g)>zm4lN{)jO041YWBV0J zHOFrn!c&j$7V5Nj)DIy$)JgIf9D+b!D_gbQd~=%DeesNg^WCG+s*gXSqW2v*jhE}r zz~vP_mqvH*8b`~ki4zUfK_oWCkxTDuOU|@`7Nril$f-WVS5C>QYh_8BmWvBIvDS8Z zd8(dG2gL9iP{T(!hPab$>Yi73a8hNe=EZ)$Ro4ce?)v8}p5<)$oA&Gq8uqG=Q(T&s z^8oY1l_r#4tVuO$#OtG@?Jq&D9#0omUENCt}c`7cV%TF9S{k>*LW+7Tg{j>tI}WaDxb0Q8Q1rC9ib)1e|0$|l*w2ydRWlr???w zgtfr_+Blozir0DfaHiDFxn{Xi$pOiDqy>RtI zK$6i`wtc3!CGgThy({?U)3a(_A)9URId7<>Sw*e2=~ESa=C^ak0`b!^^(RsMs!qlE z1)f^Ly2gQL7IS(6Olx%7NqK%rCu)AKn$_tL3p7DL#OZ&Lz8sJRIva6q*0%F>5>b+K zR%snoz{5+ARxAQ!H>{SDYbC`5(tQ$Ryo2g{rie`QnkfSw2)ISReD-Sqm(y9>$GM%g zOEvR2fdg8^&&B*OshfDGvy#s@syLSi=ZX#rJEZCO0=D4`y6vo6_U>gfFbxE5w?Ufv z6a9KVj!JDW?S_wM)#D&8FX2qNlBnn$*iaZO5LSXJw9c$fo6-dDwx?rk_~`yq4EN9~ z+s};=!xkxEl!@(R>@|aTuO(M35pi&S?joz`ZI`jt3}@xDdZWMw<5<_983O;riTR1s zl$V?vz^kIcy%S&OCQHA1gYUyyOx?KCvkrdtD1?(}Z(-*aTUt3Hvq)+ak zXG$v}mMV!oFWa|_@xAj_RWqYup^%| zJ+$5CsxmHLwI>yp$#v)T@2TeS`s-`?tTKOH#``yfUz`IdLa6rL!;>w0ZVdsm(~Zm; xs(w#hbApW| zaB^>EX>4U6ba`-PAZ2)IW&i+q+U=K7avUiPhTl0wj({YD#Bpe7s`dtZ{QNLd=flA7m|b3{ue6|Pu#e6+eoifN6j<+S(cy*%6- zFdGH4JidC~@)L4~K5u#3b}HohWT>2b<9NSOw;}fJV9PH@LJtRV8xD0F65gJ7gh$;+ zDQ?e4Xj^~m{gnoM!W@zqXU${@W8!F*3)apt8S>U=prdh_&*`xZefS<=gW+xykL~CKoPzV{{+me4MVPj3eiLsOuL1(WDj=vU&5i@scuWhxe<;0OE?J-jii-JBz+`$v*r0lttOUCEt{EJu^L=Fxq5bU_u{p15xjA=pdO2tQfh_M z3dfM+u|e)Hc>1`X5lEO^p|+sc1JfNX;%2G+!q=nSmH50&trINCNX@ z7M)V!Np3QWg;5pC2vR4UPLmi2rju9)-R$1TX^{MPxCMaVzdvjFm z$s#n%a3(bhs&ABWdS$Qkm-5p&{7&Jk^UKi7(96)v(96)v(96*O+t9$@4*170dgg{+ow!wDKmmy(W;sQT{YvvzWJPAmfH%Mx%L? z0_hw8x{MXtQfDx4k&aQYTZln20y`L*Qc_fPXj#l0^IK8y( zrGUJ6HlijV*YsiyNGOcjfN-HC0n#%nUn7u-F?=Nf;+ivl5z(HAc8rPBryg2)_jB7^ zbBZbuSMNA9;D;OpdWkrYqoZfE;;FCol%pn)wc%>tP%LX7Ic$joS#$b!4Mlr*EA4;R z44r6(U93pdez-wpOaKBP5P%doDQVrHT5~incf9DwfZuye5bj8B`w$?@rQ*GYa+Z-7 z@{1YyL;?<;zjcXNrpMYK3W&>@+Df60)-~TZ$Mt7VC`%a69_7V4vRoTP0U3xF0Ua1X zwzP!uv~vB)69xx}v<;$wjLgc6!a%m5guvhck+wlKKyIOYpHm2o;(=HhvRqI?VH7`P z<($+*^t#CR%3O99IjQGZ_HzB8A4K#s!VfUB?A3ly&K3RaDlN}d{y#lP{~#g!9%Tc> zvJhN^00cn1bS67d=G%IlwJH)jspEb21U9I_4~cEN89$^3Pdz6Pgx`$;LHOMmkmS)` zEPD%H_MVt!oGP+c78_tvFLYAJve($MH;1RL1w_kJ|F3L>-(`#{Y6Gno^FxM^&xVGq zHqy#0m0X+3WJ@p*n_Y29a zrb1H4`ytJR-+KipeL(%1d-`2+m`O4GnYtf>@Oy*dck4Z*x$wJnAksE4|Ma_$uA%X- zLf_NWWIc3JqOkc)%>obrfdHf-ARjBAwYJ5dk4;$XdTfGO!6um4Cf3+Cu{~iUWm6j| zyM`@nHLeJxC;lTj*o+^?RU`vq=C}$jl$wB;JJ4l0eBsC=LqL{bd=3RG5s2iWU^q5U zAaBz4Ysa$wFt2Imtva}PVqVkCo2A>}aAF)zTt&3Ex=Fot;hws60SJIV08)@Tg;f=V i9s(c`fB*=TKz{+0?(InkpwDvv0000 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 O0000 zaB^>EX>4U6ba`-PAZ2)IW&i+q+U=K7avUiPhTl0wj({YD#Bpe7s`dtZ{QNLd=flA7m|b3{ue6|Pu#e6+eoifN6j<+S(cy*%6- zP#XoaJidC~@)L5#dEWB2^;F39Nl`iX#_@imZbj_dz?NTzgdPUsRvhY9B)lze50AR9 zQrw=e(DwPU_g5J333EtdoHdgrjESRJE?7IqWQbdzj*iAN=(Qmq z2<3bJshspp9-fgdZqLb=Z_DrGCbx;3T)6O!(S3~bal4u_uAKL!uB)sS&K)ya>#2-x z_-OPQ?AZ&tqa+&gs3XY^^hSjk7zZWlRB6z;=0*)JCO+9PGdC7$0YV3i!foMBv(sP$ z_zu)84e20|^6d*Pvt^lUvc44{MDRf23<@Su%p^*g zqA>>}LLeQ0DqLqtd;klna)*?$Ks`wT4A(8 zbH&pY2Wma|kRu&>%26AKCM`8>xmoj8TJ7AWqmFy-*1eZrPdbrGXFB!Fr=5P5 zvks)%kdcOtJZ$(VVbq)2#`;7518TIX@gg-3+D#2ovr7fd*NIMMAjXLR+$I5%z&x2n zr<8b-o6KTiRE08})Cs54BnE=%B-TMUyEk$g@Y4J%+yX%G-yjzzx-XFXk=xIx^}2rO zWD%NWID;Am)i=sGy|UN&OZn*>es|!j^Gnf7(M!=w(M!=w(M!?)ThYMZ4*170d~97JRmq9{eW|7*_!*lCXg|5|9Sb&qixqfrX9yjPHjzr3=sfB!3WyY z;Lx_oiD3|tn~3}tk&nby=*f0o>U3M`=zdTS%LC5`E=l;CTDDtoLT=yZ0`hXO5xpt%*TilsJCLnO-{nNv-pfY)KWe5||y-Ao=xy5|{2#|HC_+6#G3nNpVHZNtAD%ZjX zNkC$gsl97&qLtbjWA;|%TKYf*WK{mQjRz{8k|{1#uB8uDKqlqGOdSES^1bVARt3aScfHBl;Avt64H|1PmJx_QmSgwT+T+P&jwD*Fl>-2kz z>N@@2qDa3J_Qpl}z4P=tgF35D{I5Fkl77!=Z&j|P53L&f(s=7_TvrvxJ@2q}j^*ibJ^Mt*m-%qsqU7JTc^-tPzhkT^po$cEj zgr7yT`HYcMv-yuGi{v5#2tZIRAb&SLJKGk2M|_eNdOcvvCYa7P!DO3QXWPUEVIyT% z8!5-SEw&o32qcL9mr88L&v6xIK-?Txp@q^D5O)Wj)wC_rY&e{l!->1d_I5X@pIy9D_bz|{1OcQWY4)~7 hv=D$GfFOW4)IW18QIKUX{bm3F002ovPDHLkV1ha~He>(* literal 0 HcmV?d00001 diff --git a/syski_client_android/app/src/main/res/drawable/cpu_icon.png b/syski_client_android/app/src/main/res/drawable/cpu_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..432bc8d8b35f3c1f52b254ac99bbb392a5b20c76 GIT binary patch literal 936 zcmV;Z16TZsP)e zSad^gZEa<4bO1wgWnpw>WFU8GbZ8()Nlj2!fese{00R(7L_t(|+U=deZQC#uhJO{g z8E9AO2wuZC?T|Hc%MLvm@CNn(86qo0fpbX@>O}#JR&*tb9}DQa7a2aLsex>^=S8sU(cI zk>|fn2#!_W?6J25atb`ZAMSa~TQ!jGPGCBxd9wm(0sxwV1-hrkVcu0v3|mP@Nw1PV z7q&uAdHbPG$DxkyJN0%ba6chw!Edbcj=^bD$37L1=ZTf53CLG^_6B4wjM{+2LdgQ8 zjVOGLK>CQ+R~8_lKI509y`)`4$2p}OR(bbB-9mkeDUeX>c-+7|b>*izEdwTZXS z|8NY$tZbI{`-@{BFNI>@s#9l48;d^cTJ)JqEQ@CIndCuD7C*c!%tZtcfFLU%zelcZ zZHvDtFQVzYg*lOGmNUQh{Ky1d(aTVEsxH+yO z7D`P(+#Tq0`}*R@BS%1P!Av<6Y>hxt9ty^>c?t4nt-rOG^&WZ8GH=!9;yLo3W!@sq zhQo*AZbcL4+-2p}s`Bmc_+Xdwt72p|AK6Z8k!3UzoPn3hfe0000< KMNUMnLSTa8jiG7) literal 0 HcmV?d00001 diff --git a/syski_client_android/app/src/main/res/drawable/cpu_thread_icon.png b/syski_client_android/app/src/main/res/drawable/cpu_thread_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..ecf7e9f2f7c13de5f2ab15ba8fbd8c33d8cea14f GIT binary patch literal 1880 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

K4Wwmj3-Z#>nEyHv6UZK0o{am1! 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 literal 0 HcmV?d00001 diff --git a/syski_client_android/app/src/main/res/drawable/ic_backup_black_24dp.xml b/syski_client_android/app/src/main/res/drawable/ic_backup_black_24dp.xml new file mode 100644 index 0000000..0862816 --- /dev/null +++ b/syski_client_android/app/src/main/res/drawable/ic_backup_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/syski_client_android/app/src/main/res/drawable/ic_expo.xml b/syski_client_android/app/src/main/res/drawable/ic_expo.xml new file mode 100644 index 0000000..6e4c260 --- /dev/null +++ b/syski_client_android/app/src/main/res/drawable/ic_expo.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + diff --git a/syski_client_android/app/src/main/res/drawable/ic_home.xml b/syski_client_android/app/src/main/res/drawable/ic_home.xml new file mode 100644 index 0000000..0f8a50a --- /dev/null +++ b/syski_client_android/app/src/main/res/drawable/ic_home.xml @@ -0,0 +1,9 @@ + + + diff --git a/syski_client_android/app/src/main/res/drawable/ic_info_black_24dp.xml b/syski_client_android/app/src/main/res/drawable/ic_info_black_24dp.xml new file mode 100644 index 0000000..34b8202 --- /dev/null +++ b/syski_client_android/app/src/main/res/drawable/ic_info_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/syski_client_android/app/src/main/res/drawable/ic_launcher_background.xml b/syski_client_android/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..d5fccc5 --- /dev/null +++ b/syski_client_android/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/syski_client_android/app/src/main/res/drawable/ic_menu_camera.xml b/syski_client_android/app/src/main/res/drawable/ic_menu_camera.xml new file mode 100644 index 0000000..0d9ea10 --- /dev/null +++ b/syski_client_android/app/src/main/res/drawable/ic_menu_camera.xml @@ -0,0 +1,12 @@ + + + + diff --git a/syski_client_android/app/src/main/res/drawable/ic_menu_gallery.xml b/syski_client_android/app/src/main/res/drawable/ic_menu_gallery.xml new file mode 100644 index 0000000..f6872c4 --- /dev/null +++ b/syski_client_android/app/src/main/res/drawable/ic_menu_gallery.xml @@ -0,0 +1,9 @@ + + + diff --git a/syski_client_android/app/src/main/res/drawable/ic_menu_manage.xml b/syski_client_android/app/src/main/res/drawable/ic_menu_manage.xml new file mode 100644 index 0000000..c1be60b --- /dev/null +++ b/syski_client_android/app/src/main/res/drawable/ic_menu_manage.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/syski_client_android/app/src/main/res/drawable/ic_menu_send.xml b/syski_client_android/app/src/main/res/drawable/ic_menu_send.xml new file mode 100644 index 0000000..00c668c --- /dev/null +++ b/syski_client_android/app/src/main/res/drawable/ic_menu_send.xml @@ -0,0 +1,9 @@ + + + diff --git a/syski_client_android/app/src/main/res/drawable/ic_menu_share.xml b/syski_client_android/app/src/main/res/drawable/ic_menu_share.xml new file mode 100644 index 0000000..a28fb9e --- /dev/null +++ b/syski_client_android/app/src/main/res/drawable/ic_menu_share.xml @@ -0,0 +1,9 @@ + + + diff --git a/syski_client_android/app/src/main/res/drawable/ic_menu_slideshow.xml b/syski_client_android/app/src/main/res/drawable/ic_menu_slideshow.xml new file mode 100644 index 0000000..209aa64 --- /dev/null +++ b/syski_client_android/app/src/main/res/drawable/ic_menu_slideshow.xml @@ -0,0 +1,9 @@ + + + diff --git a/syski_client_android/app/src/main/res/drawable/ic_notifications_black_24dp.xml b/syski_client_android/app/src/main/res/drawable/ic_notifications_black_24dp.xml new file mode 100644 index 0000000..e3400cf --- /dev/null +++ b/syski_client_android/app/src/main/res/drawable/ic_notifications_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/syski_client_android/app/src/main/res/drawable/ic_sync_black_24dp.xml b/syski_client_android/app/src/main/res/drawable/ic_sync_black_24dp.xml new file mode 100644 index 0000000..5a283aa --- /dev/null +++ b/syski_client_android/app/src/main/res/drawable/ic_sync_black_24dp.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/syski_client_android/app/src/main/res/drawable/ic_usb_black_24dp.xml b/syski_client_android/app/src/main/res/drawable/ic_usb_black_24dp.xml new file mode 100644 index 0000000..2e7e0dc --- /dev/null +++ b/syski_client_android/app/src/main/res/drawable/ic_usb_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/syski_client_android/app/src/main/res/drawable/logo_sys_144.png b/syski_client_android/app/src/main/res/drawable/logo_sys_144.png new file mode 100644 index 0000000000000000000000000000000000000000..db2a011ddb7596912f57fe47cd9c02b64af31d28 GIT binary patch literal 42140 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 `hat%W<%o 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 literal 0 HcmV?d00001 diff --git a/syski_client_android/app/src/main/res/drawable/memory_size_icon.png b/syski_client_android/app/src/main/res/drawable/memory_size_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..bcb521db4f9acd7c1b684b85cd5cf79ea1369f5a GIT binary patch literal 1661 zcmV-@27>vCP) zaB^>EX>4U6ba`-PAZ2)IW&i+q+U=K7avUiPhTl0wj({YD#Bpe7s`dtZ{QNLd=flA7m|b3{ue6|Pu#e6+eoifN6j<+S(cy*%6- zFdGH4JidC~@)L4~K5u#3b}HohWT>2b<9NSOw;}fJV9PH@LJtRV8xD0F65gJ7gh$;+ zDQ?e4Xj^~m{gnoM!W@zqXU${@W8!F*3)apt8S>U=prdh_&*`xZefS<)m-&@QieEd%k@6_WVw6a=W<6g$v&p-N!f|r>iOB$ax>?I?7r>@0i(IPi1t& zN2AYV&tA|SCDCw?Ixg9P-lz}*+w`I9&;^J9ph+>i>3Xz}& zA~beVbtU-fAKT^3yr|PT{Nb%h1cv%h1cv%h1cv%h3PZ(7@jg_{TAP2aZU# zVfNm%&;S4c24YJ`L;(K){{a7>y{D4^000SaNLh0L04^f{04^f|c%?sf00007bV*G` z2jc__5g8*|2J>J5000?uMObu0Z*6U5Zgc=ca%Ew3Wn>_CX>@2HM@dakSAh-}0008W zNklK*r__fJ6l1lfVHI z35c(o0?5E5pUT<-L>WlvJOdz#AxmS@2M{G7S~GBfC;-tchX5oHh<0s?nR-A{k5MR` zeJ#ru$cy9u-o&<4fmqv@HMA|(fcUmK>wQ@((cL|1b}OugM zbmWk!9tTqOv@6&$b31}D0c3_ic9L$_J3vspBt=pusUv^@1eM^jYJk|%>zqf4rCL@k z{|k#b)v{FjKG*dSY%juP0VI;EO8Eo_fS`Cuilk0bM*sl`vIc||Lq-TL3zvn{?is4rQ zK!UHq+EYolV&@?3~S0QHHZ-c5Cjl_AVvKGd7N%Hgbt8y00000NkvXX Hu0mjfCSU3_ literal 0 HcmV?d00001 diff --git a/syski_client_android/app/src/main/res/drawable/motherboard_icon.png b/syski_client_android/app/src/main/res/drawable/motherboard_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..6babf4f70da22b4bdaf1b05ccf38a152d359fd3e GIT binary patch literal 5962 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 literal 0 HcmV?d00001 diff --git a/syski_client_android/app/src/main/res/drawable/name_icon.png b/syski_client_android/app/src/main/res/drawable/name_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..7ac1a158e4e5e343d304d07d9b001a8c42bb924b GIT binary patch literal 1481 zcmY*YdoX{`CKEJ?E#8ZeU@s8Ok;EB=VkK)arhY$7Gv12I-iOVL?`^2YUH=nGYFA2vlzU;_5 zm64<*J#D18V1#*WwhYN^YH*q;Y8a^B8GC<7YAXqg(R*INmwVbZsD*AJ z!DTNM?V_v68fw^5m_!KL(qG-hE%E+N4f&o(V$;y#n9mj7L*Q%5&`SYZj9PsAy0Vzv zNCSuqk=01F=)hdcCup8Dc;iDLaYZS_w_0g3J}NFt5Oxj;tmq7in?aS8_w>lsb^r9OB0xevsd|ud1Uvrz*%eTE|-w z(V^fbxlj|MS?+35{ioL|>!M|~xH6NA&x{)rSYtzG2Ds(74jcde%C?Hr%o=9&Pd051 zy|+=%7|ORd`xsntbMCIFb=Wf!-df2hE?0Mf9OnP6WEW&OJ{*%>a4G8Kh2g~`79S%^ zb5N;_Uw{Uic#N(CZBSFSXjZM(9qCe%j*fit&LAzp$22W+;IE}Cg<$?4DV_jiM`88} zVN;B0&fG@Td5<7?GQ8Ea4chVpzO_k7eT`~Y=B?~6 zWY_zJ5vU`=j)476SQCKV?#6aqkdF|Ng2DM`YT#_b2dVO8Qr$vWV{Q)LG$@@?ZRT6@oJKHWUV7VA;YpKu5g z=P_MrBMSWWb{Jq84_Ibbi#FUx%3q76L2hV??1BBqBmndSRd74+Yi4`4)8?pG^6O|L zVnxllbIrqJ{UEHCVuymN1osZKQ&cF`aE!XG9LY_Oq@9b&YCO7ocFAldwhho6t?0A^ z5ujo)Jvgq50?6WjIv5~F^F5_WBB^6`3^B(;^HcjSPfM3RoZ`3aAaninxu>?EX+#ia z`pnE}NRwdmP0x(E6Azw6AAPQ3(l-eQvB5`vg8(blYH2gP;i0AQW8_-eg(TYL4xPS}C9eO$r*%tbd){CHb=SQxF% z9*q!{AvJD|AN%xE0>U&@aQ}1EQduCU|8G8xwL8Ei&#LCn%CRBBCjxAU_Jn(uK8gPT DY6pP( literal 0 HcmV?d00001 diff --git a/syski_client_android/app/src/main/res/drawable/offline_pc_icon.png b/syski_client_android/app/src/main/res/drawable/offline_pc_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..a0791d30ff4ae6b73bdd9c469ff7997fbd37572d GIT binary patch literal 3003 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 E0(`!OlK=n! literal 0 HcmV?d00001 diff --git a/syski_client_android/app/src/main/res/drawable/online_pc_icon.png b/syski_client_android/app/src/main/res/drawable/online_pc_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..d3a3bbe7758c2e81e55915dee6c9e230665de2ce GIT binary patch literal 3154 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 HE;0WCElAvY literal 0 HcmV?d00001 diff --git a/syski_client_android/app/src/main/res/drawable/pc_icon.png b/syski_client_android/app/src/main/res/drawable/pc_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..5703b38c578dc33a029d293f0063bca4d2ee24cc GIT binary patch literal 5564 zcmeHLc~}$I77vRc2%?p(1!au56_ZR>l8~^-E`ltnKx@U>0)u2R8Aw2Ek)nuCDQc;uguQK z4Tlxq|5ANhar)P$SjofszPHNM!M#^cyq{8BFlGF8k>^h$?^^lKW8Xbo6W_kQrn&l$ z)*c_n{sM_N_nWOvFEuuNJI77tcM3oK#*UT6jl{7%r$qNVmmgHT% zs}A<~2FFuDP?w+U>*_PQ%cnaJZnFJoN|^hMw}XP0~Bc}3D}BJJ#nH6;W?dK6AQ>n^32y;Yg*E8vL|P@>uc^Dy`R%k zR8CbSoXc(NPL=f3#d+{rnl}_WdQD%KlW;ZZ?OSgLt``Xx@2dFty9Z}b zjj`?!*_BxT*fdAFyJ1s1?mCVg5EoV=?WDFg1j5@Iq_>n0!(!gKar`S5!+OwBhkX3l z@{re|-`Bmqzo#(9wJdoxzcMa%Mftk(KFhZqe{aV(od)}qZ7Hi4o4x+Hw%X>8th#{W zPQNg9^`eV$lcGR}pigZLQcGcUYsiaEIOf3)R)ZhpPxSO#lQp@;>9~JuO2ND(QJ0ca zFV~*0F5Ouf5h=iON1(vfEli z;qh1i!O~}|Da6Q9>!;BaLmVN59@7EsNR1kzIT2KoNy(W^unrBA)5?Y#fpIthJvY(o zDV!4tCIWCg0KnnFu$Tq&SUd^G+#jq)M~`@`^}{LxJvl~1%i*$Nj!N}}hn@<}8jbf< z54{5Hhn#pqugTP5L}(VFrly$()n;Vs&0}Wj3EE`JI|Em8KvJg2=04$((Xk^ww2Y~w zN^A0<(dI}T8^LKabr~iMj&X<#LIo1h17_|Mc;G+c@YKUGk(-2)hG?)%nk6zs&ZHBN z;u?&^r9)p)Tr8Gw5tf7tJ_H!fV-#vQQKivm`iy@iB=+DdY>yC>H2QP})d_c~&$P4yX_zD&P}*UzSKI z=CK4KG0s9TCBcFve3T2LIFG=TCMsH3X;5sWoXKOuqn6kVgi>mBD$p;a8rK-~qe}`> zMI=xNEgM%T6u`bBUy*>v7YN0YQP4s{rw4UQvvOg!*tCFS(l9`Yfbt<#NGie6s#8r1 zAZcl^jzB1lPNC6c$eDCf5bbGd21qts=F<5Z458b>1SW90Q-*tGE|SVIO=X;Cf&YUk zAx&dY|8G2_&=Hm(9c9qymM+jOK+_3~dN$9~z#~lYV3*QUy6nimIH-Ta$%g7O0{Cil z*=F$*h-E{wp{*o?G)VK8$tOWzlAy^!m<6nHN1i|TqI z*K;ZGT;Lbg^?#Gge)Lh9P=otE19%W#X{fIO579PgRCowukbdnvx@Qxx*l8n@^$Z4k z8vV8~?heQTMq4T}I@If8Tb~QwW}m%wv3$9)ce_&Z7<)HZwVV)#rHb$OZrK*)fTMj{@Ljh8muQa z%)7LxFXyY>SLW#-PH;+|u?AW=Gr~=5RnZyTbRhSmRsVeT*T_NbXX&@*hQEvSwz-Yz zK74R@c+IScvr~J@!~0uY79VL`D@U&sbvhfn+ul#N>2;QH*ICSd*jdlM#w zm<>AO=->ufn~nJsg3QK<+tskI*F5|tsn(<9;d;)oERSo!>^CpByt&XqxAhkOxUL6& zRHVi(v~}g4JNf-aRd3&pc70#U?C(mybgAx^In*yb)Lu$WgI*KuwZLP4$lYXH6q@|7 zM7TG5Uvl1iLFB3plfo84#Q2nZXW=ibD(#7q=7BG)ER1fFvX73*lUg5adHkS4I(2)? zgU4McYR6pj#6a_J#Pbi_CM{GQW5w1FR@Sr+Hl#Wk$Mjlq@@~F6u+6W->8?lrn08Cw zI;RegyDt5fxin6OSJJe4K3Z zowxT0rv83vcT9bC)~hy^U28inVCJ>dE5-Gm+Z|4C8SsK--`hmo*{s!{i#oWya0`^5 m)HQSUk5kb6v1mSiDMjnXtmx~qZw0l+hzyMh**AAd{=WcSrX4{5 literal 0 HcmV?d00001 diff --git a/syski_client_android/app/src/main/res/drawable/ping_icon.png b/syski_client_android/app/src/main/res/drawable/ping_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..6ff02f8525c571c521e9fe27c76af5eca9ae6dc5 GIT binary patch literal 2131 zcmV-Z2(0&sP) zaB^>EX>4U6ba`-PAZ2)IW&i+q+U=K7avUiPhTl0wj({YD#Bpe7s`dtZ{QNLd=flA7m|b3{ue6|Pu#e6+eoifN6j<+S(cy*%6- zFdGH4JidC~@)L4~K5u#3b}HohWT>2b<9NSOw;}fJV9PH@LJtRV8xD0F65gJ7gh$;+ zDQ?e4Xj^~m{gnoM!W@zqXU${@W8!F*3)apt8S>U=prdh_&*`xZefS+Pqm{KeowLKu}kj9S5xOm==KlPh7%f#+eu(iR1PGNJ#J~on30Ns9?vEwM z9jYptR5h!qYtfn|&kD=g@_eIK6HBI+&CIP>4KAKsJ-fMk@mjbD-nd#&kHt$VwZdtI z<%*{(6ly*AkRu&>%26AKCM`8>xmoj8TJ7AWqmFy-*1eZrPdagx&UEUTPdoiA zXC1g|Lq-}p^048fgi&v58|x4K52(?m#*5Tcw3`~FW|s+?uM?fjK#UUsxJ?2ifq62E zPATyuH<`u4s0w8SsS{48Nel$jNvwlzc5mb~Nd7zA0zmNJAQvXOFOd6@+s~-=x_;fr|{MJW$0z-W$0z-W$0z-W$6EHXy9)L{NotD1J)$A zW{3FTwEzGB24YJ`L;(K){{a7>y{D4^000SaNLh0L04^f{04^f|c%?sf00007bV*G` z2jc`B3@Z~zpUAiX000?uMObu0Z*6U5Zgc=ca%Ew3Wn>_CX>@2HM@dakSAh-}000D@ zNkl(_pMuf zzPi6#b*s+pKA?q3jQTAE2E=4Q42UVE)_2(6XQ3%YE$bm>StbKwKukps>XJ19d;uH* z4gkA>;qbHPz@NZxz^}kv;J(I@zP(6O9cTax9geqKSqSUawZvXBke$Fx3J9IbOt?=2 zl0wr!Ge;OvX@>hYAPF>60EoEWVn7@;R|1Tv%oz|lFeE?`4P?dx4Bz%87Ra{91q#$Ga4MIcWcgEQHoXZ&BixSlfU*yfWe0%>Njt9gfd^BHWbSw$c#j=>p- zsk`V z4)tao_Z6q1@EE2U!t9!tOFkPKw103fFfNP2B60LWGYl1;zl2;-rKSrX|$PUZ;YTLY3^!#M)kA{~f^ zl(y%@`^y z1X)tA6V@3dNL7;xM6G2J2KGjR?_U=TqbDp2l4b55)fRhgmIcAT0=;tR-D|)G%3Erw zl9ojn;A;-w`&l@Qp0O-|!^lf<&Mo#Df%MaT!GUFh>|S+1w5}+(2K?0J^B?iJ-c=PX z3%W1wG_E=JT7mQv%x$$FyUP`rSyn-|btPu|B?K%0P({mv;GaI*kA1)P*lPyTNAS!& zI_?+f`Y-sNTer6rT2!QlvJ8EACkT3a_lTD?6dyJJ$8+SG3^3 zS})jDFZ2-YC)0a3S0-V1yCwr-G9bzab(8fFlL0XxCIg~OZvaT?3gMP$q0ayS002ov JPDHLkV1kx;%R2x7 literal 0 HcmV?d00001 diff --git a/syski_client_android/app/src/main/res/drawable/placeholder.png b/syski_client_android/app/src/main/res/drawable/placeholder.png new file mode 100644 index 0000000000000000000000000000000000000000..8c515b8ca2884b1cdb267567b70655c400ca5a07 GIT binary patch literal 718 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1|%QND7OGoY)RhkE)4%caKYZ?lYt_f1s;*b z3=De8Ak0{?)V>TT$X?><>&pI^iAPA#fNg{KH=vMYiEBiObAE1aYF-J0b5UwyNotBh zd1gt5g1e`0KzJjcI0FNdnWu|mNX4zUcXRs=83;5#ymsq%{LbnI*4)c!N!E{lcK(=f zkKMy@$(-YbG45?<9!(4^3c?NBK7W3BsDJgRsavZu&V7FGx;~Ea`OFopYXeG;cgAmJ zV@b@CUQ@N;h&tB`4Fp*p7c)q3Ec#fAEG9Q@zu6f4 z&(mXa?^C⁢1wU4Bg5_bPcZw; zmtf3&)UC82w8oKTOSgVo!?cgB0vF8oaWF+47k6;@{Ue1%#ZH{D^SrCWqhi4cKVpdE zTJ-b(zh85<>VaSJW6t02_~#1mU&1Lgzq{ecb0vk3J2{@;H8l@5whVV+V4A?`aQ`Q3 YAGhflf$FeGU@~LyboFyt=akR{06wH3-T(jq literal 0 HcmV?d00001 diff --git a/syski_client_android/app/src/main/res/drawable/process_icon.png b/syski_client_android/app/src/main/res/drawable/process_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..334903e415abe5304e37a73b99881f937020a444 GIT binary patch literal 2258 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 literal 0 HcmV?d00001 diff --git a/syski_client_android/app/src/main/res/drawable/restart_icon.png b/syski_client_android/app/src/main/res/drawable/restart_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..a0b1028bc8e7ef55f058b2ce0ccd3aa7cf4ccd1d GIT binary patch literal 2031 zcmV zaB^>EX>4U6ba`-PAZ2)IW&i+q+U=K7lH4c^hTl0wj({YD#Bnf~s=dJ;KR-0~^h}b; zq&5%x&`=GwkR_oHxx2&p?>{4aL*qy}BsI?^=ZKa{DqL~l@zLrUDW)~9meXFN_wsNr zz-$!E^0@W9!ts8gZbR(b!Ioc+gdPs!HXQ0UB)mQE2#>nA zQrw=m(6;{A>njcTggGQJ&YH;*#>CMq7hF5XWXM~efsV#yKBvbv^x-~Ss*ITU6q1P1 zXM}?ArAWn`Fyfvs35#J&vbPLc9HQa6TzUPxeHMARLf^>WA-%8Y7xE|4VI}lhkPn3N zJ^xfr`X&$0NEf%~!WW&tdSf~XE9WV;Fg*(kogAJIw zI6yZX2Z5CDSZKK|%UvUf<4zuMdI-h{kNl?|zM?e7EGVMrcdif@&q_lSlN?cq1T_$$ zv6HGR!B_v-CRYJLU1@e~u*PFr0RBVi?QqEgEl^a%oT{>Cht|cKA__+Bh_6scFm2nzzzw=Pn&}+;g|?z4Ut0iKBF;Q_p1R3X zz)>4A($JBI4Id?pdQ;n2f9QWejW#u2q^6?X)F3sxOwfFt=wt?BoCv^e5+Di8lUa01 zi6^hLTMy=QNMJJ2U zEW?@9D5$000JJOGiWi{{a60|De66lK=n!32;bRa{vG?BLDy{BLR4&KXw2B00(qQO+^Re z1QZe*Erqm2bpQYW8FWQhbVF}#ZDnqB07G(RVRU6=Aa`kWXdp*PO;A^X4i^9b1UyMZ zK~#9!?VYht+dve?zt9edFhM%7bRcYW03p_lkt!kB$iSL`ss9c`r_Lx+7dF(5sba8H zq#{*BY6mKHpmac4S~}QBX?kbB*yr=v@B1V#Phg+lFL&?m`TX1h@CYgxEzku7LJ=9b~egQbr;`=%Pc{<(!SSFCL z8vPl-7J>Lz2f)zMz6pRg$d}?7Cg&y)q4oicEbW>--UC?8>4Z^oEdojT6wez|JDBgi|GJ3|b;2gfQy~i0nrBi1hwwUn~ zKtJFdQ$J~=EvD2Gh;26Y9l&l45`Vi%Tg#k%c>^*`=^1r^F%8AmQ-*Z`(oI?P^-(|d zQx=ciIsh3J2;iaWI3>*zG?2rTscqe5n-U*}3?#BF`q~Wu(zYxfqfmj^0{H;V(ZF$l zFBM2+$xSs)XAXNwX+ZWZ=jk;NNY8Sw`yl}tDd`<)y<`*$kdEaX2Mq>tV7cc`2tbCG znN`y@b`aeE^Sz6`y{`?nQw2ayg}!hzj?JMvCBa-0-0!L$vbJGPAZn2*@wi>(khNp5 zT_^%_A@qq@MS;9B*e(?TxfJ@u8xYf@)hk6nu7p1E2IQr|cB2T$jnF6FfV?o+ZWRH! z75c>6wAb`xa7hu6C77Qi=JtKd0zkS&^N-GfO$?VHkCU#!65SNgh zq}&_J#2VHq(n8AfXccjB N002ovPDHLkV1hV^n)Uzy literal 0 HcmV?d00001 diff --git a/syski_client_android/app/src/main/res/drawable/shutdown_icon.png b/syski_client_android/app/src/main/res/drawable/shutdown_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..598e735d0b44d891bdf3b0ae70b021190cccfefd GIT binary patch literal 2153 zcmV-v2$uJWP) zaB^>EX>4U6ba`-PAZ2)IW&i+q+U=K7avUiPhTl0wj({YD#Bpe7s`dtZ{QNLd=flA7m|b3{ue6|Pu#e6+eoifN6j<+S(cy*%6- zFdGH4JidC~@)L4~K5u#3b}HohWT>2b<9NSOw;}d@z?NSJ2|W&o+ixnS)alOb+>IyxGc`J5ix(1-8gQf2tWr;tR9 zK0Oq;FGVWmgpsg6C2>g@W14!?Q_$iN4eN5n_4D?b=izdFBY%hVUePb)Po%>N=(Qmq z2<3bJshspp9-fgdZqLb=?~~ujP3|LZa^b=^M)xt!$L(s$xN_c?x~{TT&^u9eX z@X_cq*s~XOM@cl~QAd&;=#2_7Fbzu7snVcx&5asdOnkCoW^OFh0)!42h1I(4HKeowLKu}kh9T%+em==KlPEvzxmYuZ4@?jjILqSiF={D-Nx& zT=8^;LahfMa->6#eAwYfIcnq3q@|`UH*4NXtDU=a)N#+1AjZeSad^gZEa<4bO1wgWnpw>WFU8GbZ8()Nlj2!fese{00gy3 zL_t(|+U=cBOB_iU#(yI#n;ay91jb8nA$lJNiSODgL8DJWi0w#d(&F8Lw zbKnd(29AIOU>|r5d>WOWe(*@GabN}L0)0mrhdKJ@H&@I#vh+SDAj`lua9JY+*Kuji zwJae2uMHbD*Mwne&PIY-lO2#v;3Of4!Q;e~DH)KK13>7R`Wo;n@I!^~yfGc~Ti_7* z0Gt9}fIrP=?g0;ghrlD?G4R9;2%c6r*B_>Qzr~)Z$F3o>K+p5vdJ1d-tH5Lp=bkjb zwPk+Wrwlz)=0-MI>z#n(`kpwbdGL$`x)HDs#lpU4~k>^JZ$W!2BN%M3+?ayP%mm1(p~+Efoxt`?u^d+i@3YxF=T!73L%)pmuo|Xz`^s_kt=`~<9RoCdNmm0`g!91hs za?MlLQUZDJ=)_}9`hEuXfD3c%nOLmJqpg#Fp9;vP~z}tbA2KtRt?=lFt3sKvo?0s}K-y z+~-=UavfFQet%|({f<=R#j`s8ZB<5BtEwS|%~n5Dlht;7c2YpTYSK}Tc8Dfx(Gq(D zd{#^KRiBL~KHgbX1;~OW_F4?QJXr_<#P%5Wt^D`$WG)1d8B6R?{(E^c69UMzCH6u7 zdwDV)0?3qOA|#c2G8F>IgeCSx{(E^c;rrz(E2#dq#K#hi_pd8lUu?CESA*pp^SKTs z4#-SZfLvK(_hg}$Cs!eWoLgcKRpsGOhqMTc`yeL<-$@&X;X^H=$*2?O^OXFFv zCJXekwM|K1wgxRn7*x{b2%rT!KD8t2f*mbs`&U^kX#*|#bd9W%Ec(2WmPKc@EE<$J zAiKl?pwt>!rPcr?pUE!y3@ANQR_U2;qy%P}0Rg4>%MOT=U}gpclqNPiAfQCb*#Wt! zDM~e-5s;fcMalRx0#Y_4)^-)z(B(f()1BSW<=fjV+VZFXh&BbY>&|QnHYjbIr;R1G zv1Ao#>($JFd|PYo + + \ No newline at end of file diff --git a/syski_client_android/app/src/main/res/drawable/splash.png b/syski_client_android/app/src/main/res/drawable/splash.png new file mode 100644 index 0000000000000000000000000000000000000000..22a2f7bd00b31fb67ba5dab1845cd310edaa693f GIT binary patch literal 47388 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 literal 0 HcmV?d00001 diff --git a/syski_client_android/app/src/main/res/drawable/splashscrn.xml b/syski_client_android/app/src/main/res/drawable/splashscrn.xml new file mode 100644 index 0000000..6ec9c80 --- /dev/null +++ b/syski_client_android/app/src/main/res/drawable/splashscrn.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/syski_client_android/app/src/main/res/drawable/storage_icon.png b/syski_client_android/app/src/main/res/drawable/storage_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..fd6ed12d6093bf8cad136bd8438295a9e4345198 GIT binary patch literal 6297 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 A$^ZZW literal 0 HcmV?d00001 diff --git a/syski_client_android/app/src/main/res/layout/activity_cpu.xml b/syski_client_android/app/src/main/res/layout/activity_cpu.xml new file mode 100644 index 0000000..cb83bf0 --- /dev/null +++ b/syski_client_android/app/src/main/res/layout/activity_cpu.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/syski_client_android/app/src/main/res/layout/activity_gpu.xml b/syski_client_android/app/src/main/res/layout/activity_gpu.xml new file mode 100644 index 0000000..f5dc4df --- /dev/null +++ b/syski_client_android/app/src/main/res/layout/activity_gpu.xml @@ -0,0 +1,21 @@ + + + + + \ No newline at end of file diff --git a/syski_client_android/app/src/main/res/layout/activity_graph.xml b/syski_client_android/app/src/main/res/layout/activity_graph.xml new file mode 100644 index 0000000..da7333b --- /dev/null +++ b/syski_client_android/app/src/main/res/layout/activity_graph.xml @@ -0,0 +1,22 @@ + + + + + + diff --git a/syski_client_android/app/src/main/res/layout/activity_main.xml b/syski_client_android/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..d9d77f0 --- /dev/null +++ b/syski_client_android/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + diff --git a/syski_client_android/app/src/main/res/layout/activity_nfcreceiver.xml b/syski_client_android/app/src/main/res/layout/activity_nfcreceiver.xml new file mode 100644 index 0000000..ed81c74 --- /dev/null +++ b/syski_client_android/app/src/main/res/layout/activity_nfcreceiver.xml @@ -0,0 +1,9 @@ + + + + diff --git a/syski_client_android/app/src/main/res/layout/activity_os.xml b/syski_client_android/app/src/main/res/layout/activity_os.xml new file mode 100644 index 0000000..fcf3724 --- /dev/null +++ b/syski_client_android/app/src/main/res/layout/activity_os.xml @@ -0,0 +1,21 @@ + + + + + \ No newline at end of file diff --git a/syski_client_android/app/src/main/res/layout/activity_process_list.xml b/syski_client_android/app/src/main/res/layout/activity_process_list.xml new file mode 100644 index 0000000..3c18caf --- /dev/null +++ b/syski_client_android/app/src/main/res/layout/activity_process_list.xml @@ -0,0 +1,21 @@ + + + + + diff --git a/syski_client_android/app/src/main/res/layout/activity_qrscan_menu.xml b/syski_client_android/app/src/main/res/layout/activity_qrscan_menu.xml new file mode 100644 index 0000000..58a8997 --- /dev/null +++ b/syski_client_android/app/src/main/res/layout/activity_qrscan_menu.xml @@ -0,0 +1,35 @@ + + + + + +