├── .editorconfig ├── .github ├── FUNDING.yml └── workflows │ ├── changelog.yml │ ├── checkcode.yml │ └── release.yml ├── .gitignore ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── rustfmt.toml ├── src ├── cache.rs ├── cache │ └── lru.rs ├── disk_cache.rs ├── lib.rs ├── meter.rs └── meter │ ├── count_meter.rs │ ├── file_meter.rs │ └── heap_meter.rs └── tests ├── disk_cache_test.rs └── lru_test.rs /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 4 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | 10 | [*.md] 11 | indent_size = 2 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | custom: ['https://paypal.me/psiace', 'https://afdian.net/@psiace'] 4 | -------------------------------------------------------------------------------- /.github/workflows/changelog.yml: -------------------------------------------------------------------------------- 1 | name: Generate changelog 2 | on: 3 | release: 4 | types: [created, edited] 5 | 6 | jobs: 7 | generate-changelog: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@master 11 | with: 12 | fetch-depth: 0 13 | - uses: BobAnkh/auto-generate-changelog@master 14 | with: 15 | REPO_NAME: 'ritelabs/ritecache' 16 | ACCESS_TOKEN: ${{secrets.GITHUB_TOKEN}} 17 | PATH: '/CHANGELOG.md' 18 | COMMIT_MESSAGE: 'docs(changlog): auto update by ci' 19 | TYPE: 'feat:Feature,fix:Bug Fixes,docs:Documentation,refactor:Refactor,perf:Performance Improvements' 20 | -------------------------------------------------------------------------------- /.github/workflows/checkcode.yml: -------------------------------------------------------------------------------- 1 | name: Check Code 2 | 3 | on: 4 | push: 5 | branches: 6 | - '*' 7 | - '!staging.tmp' 8 | tags: 9 | - '*' 10 | 11 | jobs: 12 | test: 13 | name: "Test" 14 | 15 | strategy: 16 | matrix: 17 | os: [ 18 | ubuntu-latest, 19 | macos-latest, 20 | windows-latest 21 | ] 22 | rust: [nightly, beta, stable] 23 | 24 | runs-on: ${{ matrix.os }} 25 | 26 | steps: 27 | - name: "Checkout Repository" 28 | uses: actions/checkout@master 29 | 30 | - name: Install Rust toolchain 31 | uses: actions-rs/toolchain@v1 32 | with: 33 | profile: minimal 34 | toolchain: ${{ matrix.rust }} 35 | override: true 36 | 37 | - name: Cache cargo registry 38 | uses: actions/cache@v1 39 | with: 40 | path: ~/.cargo/registry 41 | key: ${{ matrix.os }}-${{ matrix.rust }}-cargo-registry-${{ hashFiles('**/Cargo.toml') }} 42 | 43 | - name: Cache cargo index 44 | uses: actions/cache@v1 45 | with: 46 | path: ~/.cargo/git 47 | key: ${{ matrix.os }}-${{ matrix.rust }}-cargo-index-${{ hashFiles('**/Cargo.toml') }} 48 | 49 | - name: Cache cargo build 50 | uses: actions/cache@v1 51 | with: 52 | path: target 53 | key: ${{ matrix.os }}-${{ matrix.rust }}-cargo-build-target-${{ hashFiles('**/Cargo.toml') }} 54 | 55 | - name: Release build 56 | uses: actions-rs/cargo@v1 57 | with: 58 | command: build 59 | args: --release 60 | 61 | - name: Release build without default features 62 | uses: actions-rs/cargo@v1 63 | with: 64 | command: build 65 | args: --release --no-default-features 66 | 67 | - name: Cargo test 68 | uses: actions-rs/cargo@v1 69 | with: 70 | command: test 71 | 72 | - name: Cargo test without default features 73 | uses: actions-rs/cargo@v1 74 | with: 75 | command: test 76 | args: --no-default-features 77 | 78 | check: 79 | name: "Clippy & Format" 80 | runs-on: ubuntu-latest 81 | 82 | steps: 83 | - name: "Checkout Repository" 84 | uses: actions/checkout@master 85 | 86 | - name: Install Rust toolchain 87 | uses: actions-rs/toolchain@v1 88 | with: 89 | profile: minimal 90 | toolchain: stable 91 | components: rustfmt, clippy 92 | override: true 93 | 94 | - name: Clippy warnings 95 | uses: actions-rs/cargo@v1 96 | with: 97 | command: clippy 98 | args: -- -D warnings 99 | 100 | - name: Cargo Fmt Check 101 | uses: actions-rs/cargo@v1 102 | with: 103 | command: fmt 104 | args: --all -- --check 105 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Auto Release 2 | 3 | on: 4 | push: 5 | # Sequence of patterns matched against refs/tags 6 | tags: 7 | - "v*" # Push events to matching v*, i.e. v1.0, v20.15.10 8 | 9 | jobs: 10 | release: 11 | name: Auto Release by Tags 12 | runs-on: ubuntu-18.04 13 | 14 | steps: 15 | - name: Checkout Repository 16 | uses: actions/checkout@master 17 | 18 | - name: Install Rust toolchain 19 | uses: actions-rs/toolchain@v1 20 | with: 21 | profile: minimal 22 | toolchain: stable 23 | default: true 24 | 25 | - name: Cargo Login 26 | uses: actions-rs/cargo@v1 27 | with: 28 | command: login 29 | args: -- ${{ secrets.RITEDB_CARGO }} 30 | 31 | - name: Cargo Publish 32 | uses: actions-rs/cargo@v1 33 | with: 34 | command: publish 35 | 36 | - name: GitHub Release 37 | id: create_release 38 | uses: actions/create-release@v1 39 | env: 40 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token 41 | with: 42 | tag_name: ${{ github.ref }} 43 | release_name: Release ${{ github.ref }} 44 | draft: false 45 | prerelease: false 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ritecache" 3 | version = "0.1.0" 4 | authors = ["Chojan Shang ", "Datafuse Authors "] 5 | edition = "2018" 6 | license = "MIT/Apache-2.0" 7 | repository = "https://github.com/ritedb/ritecache" 8 | description = "RiteCache. A smart cache that knows your data." 9 | 10 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 11 | 12 | [features] 13 | heapsize = ["heapsize_"] 14 | amortized = ["ritelinked/ahash-amortized", "ritelinked/inline-more-amortized"] 15 | 16 | [dependencies] 17 | filetime = "0.2.15" 18 | heapsize_ = { package = "heapsize", version = "0.4.2", optional = true} 19 | log = "0.4" 20 | ritelinked = { version = "0.3.2", default-features = false, features = ["ahash", "inline-more"] } 21 | walkdir = "2.3.2" 22 | 23 | [dev-dependencies] 24 | tempfile = "3" 25 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Chojan Shang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RiteCache - The Smart Cache Knows Your Data 2 | 3 | ritecache attempts to provide a convenient and usable cache. 4 | 5 | In addition to the basic caching policy provided by default, users can design their own implementation of caching qualities and gain disk caching capabilities as needed. 6 | 7 | ## Contact 8 | 9 | Chojan Shang - [@PsiACE](https://github.com/psiace) - 10 | 11 | Project Link: [https://github.com/ritelabs/ritecache](https://github.com/ritelabs/ritecache) 12 | 13 | ## Credit 14 | 15 | It is a fork of [the popular diskcache in sccache](https://github.com/mozilla/sccache/tree/master/src/lru_disk_cache), but more adjustments and improvements have been made to the code . 16 | 17 | ## License 18 | 19 | Licensed under either of: 20 | 21 | - Apache License, Version 2.0 ([LICENSE-APACHE](./LICENSE-APACHE) or [http://apache.org/licenses/LICENSE-2.0](http://apache.org/licenses/LICENSE-2.0)) 22 | - MIT license ([LICENSE-MIT](./LICENSE-MIT) or [http://opensource.org/licenses/MIT](http://opensource.org/licenses/MIT)) 23 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | edition = "2018" 2 | max_width = 100 3 | newline_style = "Unix" 4 | reorder_imports = true 5 | tab_spaces = 4 6 | use_field_init_shorthand = true 7 | use_small_heuristics = "Max" 8 | -------------------------------------------------------------------------------- /src/cache.rs: -------------------------------------------------------------------------------- 1 | pub mod lru; 2 | 3 | use std::borrow::Borrow; 4 | use std::hash::{BuildHasher, Hash}; 5 | 6 | use crate::Meter; 7 | 8 | /// A trait for a cache. 9 | pub trait Cache 10 | where 11 | K: Eq + Hash, 12 | S: BuildHasher, 13 | M: Meter, 14 | { 15 | fn with_meter_and_hasher(cap: u64, meter: M, hash_builder: S) -> Self; 16 | fn get<'a, Q>(&'a mut self, k: &Q) -> Option<&'a V> 17 | where 18 | K: Borrow, 19 | Q: Hash + Eq + ?Sized; 20 | fn get_mut<'a, Q>(&'a mut self, k: &Q) -> Option<&'a mut V> 21 | where 22 | K: Borrow, 23 | Q: Hash + Eq + ?Sized; 24 | fn peek<'a, Q>(&'a self, k: &Q) -> Option<&'a V> 25 | where 26 | K: Borrow, 27 | Q: Hash + Eq + ?Sized; 28 | fn peek_mut<'a, Q>(&'a mut self, k: &Q) -> Option<&'a mut V> 29 | where 30 | K: Borrow, 31 | Q: Hash + Eq + ?Sized; 32 | fn peek_by_policy(&self) -> Option<(&K, &V)>; 33 | fn put(&mut self, k: K, v: V) -> Option; 34 | fn pop(&mut self, k: &Q) -> Option 35 | where 36 | K: Borrow, 37 | Q: Hash + Eq + ?Sized; 38 | fn pop_by_policy(&mut self) -> Option<(K, V)>; 39 | fn contains(&self, k: &Q) -> bool 40 | where 41 | K: Borrow, 42 | Q: Hash + Eq + ?Sized; 43 | fn len(&self) -> usize; 44 | fn is_empty(&self) -> bool; 45 | fn capacity(&self) -> u64; 46 | fn set_capacity(&mut self, cap: u64); 47 | fn size(&self) -> u64; 48 | fn clear(&mut self); 49 | } 50 | -------------------------------------------------------------------------------- /src/cache/lru.rs: -------------------------------------------------------------------------------- 1 | //! A cache that holds a limited number of key-value pairs. When the 2 | //! capacity of the cache is exceeded, the least-recently-used 3 | //! (where "used" means a look-up or putting the pair into the cache) 4 | //! pair is automatically removed. 5 | //! 6 | //! # Examples 7 | //! 8 | //! ```rust,ignore 9 | //! use ritecache::{Cache, LruCache}; 10 | //! 11 | //! let mut cache = LruCache::new(2); 12 | //! 13 | //! cache.put(1, 10); 14 | //! cache.put(2, 20); 15 | //! cache.put(3, 30); 16 | //! assert!(cache.get_mut(&1).is_none()); 17 | //! assert_eq!(*cache.get_mut(&2).unwrap(), 20); 18 | //! assert_eq!(*cache.get_mut(&3).unwrap(), 30); 19 | //! 20 | //! cache.put(2, 22); 21 | //! assert_eq!(*cache.get_mut(&2).unwrap(), 22); 22 | //! 23 | //! cache.put(6, 60); 24 | //! assert!(cache.get_mut(&3).is_none()); 25 | //! 26 | //! cache.set_capacity(1); 27 | //! assert!(cache.get_mut(&2).is_none()); 28 | //! ``` 29 | //! 30 | //! The cache can also be limited by an arbitrary metric calculated from its key-value pairs, see 31 | //! [`LruCache::with_meter`][with_meter] for more information. If the `heapsize` feature is enabled, 32 | //! this crate provides one such alternate metric—`HeapSize`. Custom metrics can be written by 33 | //! implementing the [`Meter`][meter] trait. 34 | //! 35 | //! [with_meter]: struct.LruCache.html#method.with_meter 36 | //! [meter]: trait.Meter.html 37 | 38 | #[cfg(feature = "heapsize")] 39 | extern crate heapsize_; 40 | 41 | use std::borrow::Borrow; 42 | use std::fmt; 43 | use std::hash::BuildHasher; 44 | use std::hash::Hash; 45 | 46 | use ritelinked::linked_hash_map; 47 | use ritelinked::DefaultHashBuilder; 48 | use ritelinked::LinkedHashMap; 49 | 50 | use crate::cache::Cache; 51 | use crate::meter::count_meter::Count; 52 | use crate::meter::count_meter::CountableMeter; 53 | 54 | /// An LRU cache. 55 | #[derive(Clone)] 56 | pub struct LruCache< 57 | K: Eq + Hash, 58 | V, 59 | S: BuildHasher = DefaultHashBuilder, 60 | M: CountableMeter = Count, 61 | > { 62 | map: LinkedHashMap, 63 | current_measure: M::Measure, 64 | max_capacity: u64, 65 | meter: M, 66 | } 67 | 68 | impl LruCache { 69 | /// Creates an empty cache that can hold at most `capacity` items. 70 | /// 71 | /// # Examples 72 | /// 73 | /// ```rust,ignore 74 | /// use ritecache::{Cache, LruCache}; 75 | /// let mut cache: LruCache = LruCache::new(10); 76 | /// ``` 77 | pub fn new(capacity: u64) -> Self { 78 | LruCache { 79 | map: LinkedHashMap::new(), 80 | current_measure: (), 81 | max_capacity: capacity, 82 | meter: Count, 83 | } 84 | } 85 | } 86 | 87 | impl> LruCache { 88 | /// Creates an empty cache that can hold at most `capacity` as measured by `meter`. 89 | /// 90 | /// You can implement the [`Meter`][meter] trait to allow custom metrics. 91 | /// 92 | /// [meter]: trait.Meter.html 93 | /// 94 | /// # Examples 95 | /// 96 | /// ```rust,ignore 97 | /// use ritecache::{Cache, LruCache, Meter}; 98 | /// use std::borrow::Borrow; 99 | /// 100 | /// /// Measure Vec items by their length 101 | /// struct VecLen; 102 | /// 103 | /// impl Meter> for VecLen { 104 | /// // Use `Measure = usize` or implement `CountableMeter` as well. 105 | /// type Measure = usize; 106 | /// fn measure(&self, _: &Q, v: &Vec) -> usize 107 | /// where K: Borrow 108 | /// { 109 | /// v.len() 110 | /// } 111 | /// } 112 | /// 113 | /// let mut cache = LruCache::with_meter(5, VecLen); 114 | /// cache.put(1, vec![1, 2]); 115 | /// assert_eq!(cache.size(), 2); 116 | /// cache.put(2, vec![3, 4]); 117 | /// cache.put(3, vec![5, 6]); 118 | /// assert_eq!(cache.size(), 4); 119 | /// assert_eq!(cache.len(), 2); 120 | /// ``` 121 | pub fn with_meter(capacity: u64, meter: M) -> LruCache { 122 | LruCache { 123 | map: LinkedHashMap::new(), 124 | current_measure: Default::default(), 125 | max_capacity: capacity, 126 | meter, 127 | } 128 | } 129 | } 130 | 131 | impl LruCache { 132 | /// Creates an empty cache that can hold at most `capacity` items with the given hash builder. 133 | pub fn with_hasher(capacity: u64, hash_builder: S) -> LruCache { 134 | LruCache { 135 | map: LinkedHashMap::with_hasher(hash_builder), 136 | current_measure: (), 137 | max_capacity: capacity, 138 | meter: Count, 139 | } 140 | } 141 | } 142 | 143 | impl> Cache 144 | for LruCache 145 | { 146 | /// Creates an empty cache that can hold at most `capacity` as measured by `meter` with the 147 | /// given hash builder. 148 | fn with_meter_and_hasher(capacity: u64, meter: M, hash_builder: S) -> Self { 149 | LruCache { 150 | map: LinkedHashMap::with_hasher(hash_builder), 151 | current_measure: Default::default(), 152 | max_capacity: capacity, 153 | meter, 154 | } 155 | } 156 | 157 | /// Returns a reference to the value corresponding to the given key in the cache, if 158 | /// any. 159 | /// 160 | /// Note that this method is not available for cache objects using `Meter` implementations 161 | /// other than `Count`. 162 | /// 163 | /// # Examples 164 | /// 165 | /// ```rust,ignore 166 | /// use ritecache::{Cache, LruCache}; 167 | /// 168 | /// let mut cache = LruCache::new(2); 169 | /// 170 | /// cache.put(1, "a"); 171 | /// cache.put(2, "b"); 172 | /// cache.put(2, "c"); 173 | /// cache.put(3, "d"); 174 | /// 175 | /// assert_eq!(cache.get(&1), None); 176 | /// assert_eq!(cache.get(&2), Some(&"c")); 177 | /// ``` 178 | fn get(&mut self, k: &Q) -> Option<&V> 179 | where 180 | K: Borrow, 181 | Q: Hash + Eq + ?Sized, 182 | { 183 | self.map.get_refresh(k).map(|v| v as &V) 184 | } 185 | 186 | /// Returns a mutable reference to the value corresponding to the given key in the cache, if 187 | /// any. 188 | /// 189 | /// Note that this method is not available for cache objects using `Meter` implementations 190 | /// other than `Count`. 191 | /// 192 | /// # Examples 193 | /// 194 | /// ```rust,ignore 195 | /// use ritecache::{Cache, LruCache}; 196 | /// 197 | /// let mut cache = LruCache::new(2); 198 | /// 199 | /// cache.put(1, "a"); 200 | /// cache.put(2, "b"); 201 | /// cache.put(2, "c"); 202 | /// cache.put(3, "d"); 203 | /// 204 | /// assert_eq!(cache.get_mut(&1), None); 205 | /// assert_eq!(cache.get_mut(&2), Some(&mut "c")); 206 | /// ``` 207 | fn get_mut(&mut self, k: &Q) -> Option<&mut V> 208 | where 209 | K: Borrow, 210 | Q: Hash + Eq + ?Sized, 211 | { 212 | self.map.get_refresh(k) 213 | } 214 | 215 | /// Returns a reference to the value corresponding to the key in the cache or `None` if it is 216 | /// not present in the cache. Unlike `get`, `peek` does not update the LRU list so the key's 217 | /// position will be unchanged. 218 | /// 219 | /// # Example 220 | /// 221 | /// ```rust,ignore 222 | /// use ritecache::{Cache, LruCache}; 223 | /// let mut cache = LruCache::new(2); 224 | /// 225 | /// cache.put(1, "a"); 226 | /// cache.put(2, "b"); 227 | /// 228 | /// assert_eq!(cache.peek(&1), Some(&"a")); 229 | /// assert_eq!(cache.peek(&2), Some(&"b")); 230 | /// ``` 231 | fn peek<'a, Q>(&'a self, k: &Q) -> Option<&'a V> 232 | where 233 | K: Borrow, 234 | Q: Hash + Eq + ?Sized, 235 | { 236 | self.map.get(k) 237 | } 238 | 239 | /// Returns a mutable reference to the value corresponding to the key in the cache or `None` 240 | /// if it is not present in the cache. Unlike `get_mut`, `peek_mut` does not update the LRU 241 | /// list so the key's position will be unchanged. 242 | /// 243 | /// # Example 244 | /// 245 | /// ```rust,ignore 246 | /// use ritecache::{Cache, LruCache}; 247 | /// let mut cache = LruCache::new(2); 248 | /// 249 | /// cache.put(1, "a"); 250 | /// cache.put(2, "b"); 251 | /// 252 | /// assert_eq!(cache.peek_mut(&1), Some(&mut "a")); 253 | /// assert_eq!(cache.peek_mut(&2), Some(&mut "b")); 254 | /// ``` 255 | fn peek_mut<'a, Q>(&'a mut self, k: &Q) -> Option<&'a mut V> 256 | where 257 | K: Borrow, 258 | Q: Hash + Eq + ?Sized, 259 | { 260 | self.map.get_mut(k) 261 | } 262 | 263 | /// Returns the value corresponding to the least recently used item or `None` if the 264 | /// cache is empty. Like `peek`, `peek_lru` does not update the LRU list so the item's 265 | /// position will be unchanged. 266 | /// 267 | /// # Example 268 | /// 269 | /// ```rust,ignore 270 | /// use ritecache::{Cache, LruCache}; 271 | /// let mut cache = LruCache::new(2); 272 | /// 273 | /// cache.put(1, "a"); 274 | /// cache.put(2, "b"); 275 | /// 276 | /// assert_eq!(cache.peek_by_policy(), Some((&1, &"a"))); 277 | /// ``` 278 | fn peek_by_policy(&self) -> Option<(&K, &V)> { 279 | self.map.front() 280 | } 281 | 282 | /// Checks if the map contains the given key. 283 | /// 284 | /// # Examples 285 | /// 286 | /// ```rust,ignore 287 | /// use ritecache::{Cache, LruCache}; 288 | /// 289 | /// let mut cache = LruCache::new(1); 290 | /// 291 | /// cache.put(1, "a"); 292 | /// assert!(cache.contains(&1)); 293 | /// ``` 294 | fn contains(&self, key: &Q) -> bool 295 | where 296 | K: Borrow, 297 | Q: Hash + Eq, 298 | { 299 | self.map.contains_key(key) 300 | } 301 | 302 | /// Inserts a key-value pair into the cache. If the key already existed, the old value is 303 | /// returned. 304 | /// 305 | /// # Examples 306 | /// 307 | /// ```rust,ignore 308 | /// use ritecache::{Cache, LruCache}; 309 | /// 310 | /// let mut cache = LruCache::new(2); 311 | /// 312 | /// cache.put(1, "a"); 313 | /// cache.put(2, "b"); 314 | /// assert_eq!(cache.get_mut(&1), Some(&mut "a")); 315 | /// assert_eq!(cache.get_mut(&2), Some(&mut "b")); 316 | /// ``` 317 | fn put(&mut self, k: K, v: V) -> Option { 318 | let new_size = self.meter.measure(&k, &v); 319 | self.current_measure = self.meter.add(self.current_measure, new_size); 320 | if let Some(old) = self.map.get(&k) { 321 | self.current_measure = 322 | self.meter.sub(self.current_measure, self.meter.measure(&k, old)); 323 | } 324 | let old_val = self.map.insert(k, v); 325 | while self.size() > self.capacity() { 326 | self.pop_by_policy(); 327 | } 328 | old_val 329 | } 330 | 331 | /// Removes the given key from the cache and returns its corresponding value. 332 | /// 333 | /// # Examples 334 | /// 335 | /// ```rust,ignore 336 | /// use ritecache::{Cache, LruCache}; 337 | /// 338 | /// let mut cache = LruCache::new(2); 339 | /// 340 | /// cache.put(2, "a"); 341 | /// 342 | /// assert_eq!(cache.pop(&1), None); 343 | /// assert_eq!(cache.pop(&2), Some("a")); 344 | /// assert_eq!(cache.pop(&2), None); 345 | /// assert_eq!(cache.len(), 0); 346 | /// ``` 347 | fn pop(&mut self, k: &Q) -> Option 348 | where 349 | K: Borrow, 350 | Q: Hash + Eq + ?Sized, 351 | { 352 | self.map.remove(k).map(|v| { 353 | self.current_measure = self.meter.sub(self.current_measure, self.meter.measure(k, &v)); 354 | v 355 | }) 356 | } 357 | 358 | /// Removes and returns the least recently used key-value pair as a tuple. 359 | /// 360 | /// # Examples 361 | /// 362 | /// ```rust,ignore 363 | /// use ritecache::{Cache, LruCache}; 364 | /// 365 | /// let mut cache = LruCache::new(2); 366 | /// 367 | /// cache.put(1, "a"); 368 | /// cache.put(2, "b"); 369 | /// 370 | /// assert_eq!(cache.pop_by_policy(), Some((1, "a"))); 371 | /// assert_eq!(cache.len(), 1); 372 | /// ``` 373 | #[inline] 374 | fn pop_by_policy(&mut self) -> Option<(K, V)> { 375 | self.map.pop_front().map(|(k, v)| { 376 | self.current_measure = self.meter.sub(self.current_measure, self.meter.measure(&k, &v)); 377 | (k, v) 378 | }) 379 | } 380 | 381 | /// Sets the size of the key-value pairs the cache can hold, as measured by the `Meter` used by 382 | /// the cache. 383 | /// 384 | /// Removes least-recently-used key-value pairs if necessary. 385 | /// 386 | /// # Examples 387 | /// 388 | /// ```rust,ignore 389 | /// use ritecache::{Cache, LruCache}; 390 | /// 391 | /// let mut cache = LruCache::new(2); 392 | /// 393 | /// cache.put(1, "a"); 394 | /// cache.put(2, "b"); 395 | /// cache.put(3, "c"); 396 | /// 397 | /// assert_eq!(cache.get_mut(&1), None); 398 | /// assert_eq!(cache.get_mut(&2), Some(&mut "b")); 399 | /// assert_eq!(cache.get_mut(&3), Some(&mut "c")); 400 | /// 401 | /// cache.set_capacity(3); 402 | /// cache.put(1, "a"); 403 | /// cache.put(2, "b"); 404 | /// 405 | /// assert_eq!(cache.get_mut(&1), Some(&mut "a")); 406 | /// assert_eq!(cache.get_mut(&2), Some(&mut "b")); 407 | /// assert_eq!(cache.get_mut(&3), Some(&mut "c")); 408 | /// 409 | /// cache.set_capacity(1); 410 | /// 411 | /// assert_eq!(cache.get_mut(&1), None); 412 | /// assert_eq!(cache.get_mut(&2), None); 413 | /// assert_eq!(cache.get_mut(&3), Some(&mut "c")); 414 | /// ``` 415 | fn set_capacity(&mut self, capacity: u64) { 416 | while self.size() > capacity { 417 | self.pop_by_policy(); 418 | } 419 | self.max_capacity = capacity; 420 | } 421 | 422 | /// Returns the number of key-value pairs in the cache. 423 | fn len(&self) -> usize { 424 | self.map.len() 425 | } 426 | 427 | /// Returns the size of all the key-value pairs in the cache, as measured by the `Meter` used 428 | /// by the cache. 429 | fn size(&self) -> u64 { 430 | self.meter.size(self.current_measure).unwrap_or_else(|| self.map.len() as u64) 431 | } 432 | 433 | /// Returns the maximum size of the key-value pairs the cache can hold, as measured by the 434 | /// `Meter` used by the cache. 435 | /// 436 | /// # Examples 437 | /// 438 | /// ```rust,ignore 439 | /// use ritecache::{Cache, LruCache}; 440 | /// let mut cache: LruCache = LruCache::new(2); 441 | /// assert_eq!(cache.capacity(), 2); 442 | /// ``` 443 | fn capacity(&self) -> u64 { 444 | self.max_capacity 445 | } 446 | 447 | /// Returns `true` if the cache contains no key-value pairs. 448 | fn is_empty(&self) -> bool { 449 | self.map.is_empty() 450 | } 451 | 452 | /// Removes all key-value pairs from the cache. 453 | fn clear(&mut self) { 454 | self.map.clear(); 455 | self.current_measure = Default::default(); 456 | } 457 | } 458 | 459 | impl> LruCache { 460 | /// Returns an iterator over the cache's key-value pairs in least- to most-recently-used order. 461 | /// 462 | /// Accessing the cache through the iterator does _not_ affect the cache's LRU state. 463 | /// 464 | /// # Examples 465 | /// 466 | /// ```rust,ignore 467 | /// use ritecache::{Cache, LruCache}; 468 | /// 469 | /// let mut cache = LruCache::new(2); 470 | /// 471 | /// cache.put(1, 10); 472 | /// cache.put(2, 20); 473 | /// cache.put(3, 30); 474 | /// 475 | /// let kvs: Vec<_> = cache.iter().collect(); 476 | /// assert_eq!(kvs, [(&2, &20), (&3, &30)]); 477 | /// ``` 478 | pub fn iter(&self) -> Iter<'_, K, V> { 479 | Iter(self.map.iter()) 480 | } 481 | 482 | /// Returns an iterator over the cache's key-value pairs in least- to most-recently-used order, 483 | /// with mutable references to the values. 484 | /// 485 | /// Accessing the cache through the iterator does _not_ affect the cache's LRU state. 486 | /// Note that this method is not available for cache objects using `Meter` implementations. 487 | /// other than `Count`. 488 | /// 489 | /// # Examples 490 | /// 491 | /// ```rust,ignore 492 | /// use ritecache::{Cache, LruCache}; 493 | /// 494 | /// let mut cache = LruCache::new(2); 495 | /// 496 | /// cache.put(1, 10); 497 | /// cache.put(2, 20); 498 | /// cache.put(3, 30); 499 | /// 500 | /// let mut n = 2; 501 | /// 502 | /// for (k, v) in cache.iter_mut() { 503 | /// assert_eq!(*k, n); 504 | /// assert_eq!(*v, n * 10); 505 | /// *v *= 10; 506 | /// n += 1; 507 | /// } 508 | /// 509 | /// assert_eq!(n, 4); 510 | /// assert_eq!(cache.get_mut(&2), Some(&mut 200)); 511 | /// assert_eq!(cache.get_mut(&3), Some(&mut 300)); 512 | /// ``` 513 | pub fn iter_mut(&mut self) -> IterMut<'_, K, V> { 514 | IterMut(self.map.iter_mut()) 515 | } 516 | } 517 | 518 | impl> Extend<(K, V)> 519 | for LruCache 520 | { 521 | fn extend>(&mut self, iter: I) { 522 | for (k, v) in iter { 523 | self.put(k, v); 524 | } 525 | } 526 | } 527 | 528 | impl> fmt::Debug 529 | for LruCache 530 | { 531 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 532 | f.debug_map().entries(self.iter().rev()).finish() 533 | } 534 | } 535 | 536 | impl> IntoIterator 537 | for LruCache 538 | { 539 | type Item = (K, V); 540 | type IntoIter = IntoIter; 541 | 542 | fn into_iter(self) -> IntoIter { 543 | IntoIter(self.map.into_iter()) 544 | } 545 | } 546 | 547 | impl<'a, K: Eq + Hash, V, S: BuildHasher, M: CountableMeter> IntoIterator 548 | for &'a LruCache 549 | { 550 | type Item = (&'a K, &'a V); 551 | type IntoIter = Iter<'a, K, V>; 552 | fn into_iter(self) -> Iter<'a, K, V> { 553 | self.iter() 554 | } 555 | } 556 | 557 | impl<'a, K: Eq + Hash, V, S: BuildHasher, M: CountableMeter> IntoIterator 558 | for &'a mut LruCache 559 | { 560 | type Item = (&'a K, &'a mut V); 561 | type IntoIter = IterMut<'a, K, V>; 562 | fn into_iter(self) -> IterMut<'a, K, V> { 563 | self.iter_mut() 564 | } 565 | } 566 | 567 | /// An iterator over a cache's key-value pairs in least- to most-recently-used order. 568 | /// 569 | /// # Examples 570 | /// 571 | /// ```rust,ignore 572 | /// use ritecache::{Cache, LruCache}; 573 | /// 574 | /// let mut cache = LruCache::new(2); 575 | /// 576 | /// cache.put(1, 10); 577 | /// cache.put(2, 20); 578 | /// cache.put(3, 30); 579 | /// 580 | /// let mut n = 2; 581 | /// 582 | /// for (k, v) in cache { 583 | /// assert_eq!(k, n); 584 | /// assert_eq!(v, n * 10); 585 | /// n += 1; 586 | /// } 587 | /// 588 | /// assert_eq!(n, 4); 589 | /// ``` 590 | pub struct IntoIter(linked_hash_map::IntoIter); 591 | 592 | impl Iterator for IntoIter { 593 | type Item = (K, V); 594 | 595 | fn next(&mut self) -> Option<(K, V)> { 596 | self.0.next() 597 | } 598 | 599 | fn size_hint(&self) -> (usize, Option) { 600 | self.0.size_hint() 601 | } 602 | } 603 | 604 | impl DoubleEndedIterator for IntoIter { 605 | fn next_back(&mut self) -> Option<(K, V)> { 606 | self.0.next_back() 607 | } 608 | } 609 | 610 | impl ExactSizeIterator for IntoIter { 611 | fn len(&self) -> usize { 612 | self.0.len() 613 | } 614 | } 615 | 616 | /// An iterator over a cache's key-value pairs in least- to most-recently-used order. 617 | /// 618 | /// Accessing a cache through the iterator does _not_ affect the cache's LRU state. 619 | pub struct Iter<'a, K, V>(linked_hash_map::Iter<'a, K, V>); 620 | 621 | impl<'a, K, V> Clone for Iter<'a, K, V> { 622 | fn clone(&self) -> Iter<'a, K, V> { 623 | Iter(self.0.clone()) 624 | } 625 | } 626 | 627 | impl<'a, K, V> Iterator for Iter<'a, K, V> { 628 | type Item = (&'a K, &'a V); 629 | fn next(&mut self) -> Option<(&'a K, &'a V)> { 630 | self.0.next() 631 | } 632 | fn size_hint(&self) -> (usize, Option) { 633 | self.0.size_hint() 634 | } 635 | } 636 | 637 | impl<'a, K, V> DoubleEndedIterator for Iter<'a, K, V> { 638 | fn next_back(&mut self) -> Option<(&'a K, &'a V)> { 639 | self.0.next_back() 640 | } 641 | } 642 | 643 | impl<'a, K, V> ExactSizeIterator for Iter<'a, K, V> { 644 | fn len(&self) -> usize { 645 | self.0.len() 646 | } 647 | } 648 | 649 | /// An iterator over a cache's key-value pairs in least- to most-recently-used order with mutable 650 | /// references to the values. 651 | /// 652 | /// Accessing a cache through the iterator does _not_ affect the cache's LRU state. 653 | pub struct IterMut<'a, K, V>(linked_hash_map::IterMut<'a, K, V>); 654 | 655 | impl<'a, K, V> Iterator for IterMut<'a, K, V> { 656 | type Item = (&'a K, &'a mut V); 657 | fn next(&mut self) -> Option<(&'a K, &'a mut V)> { 658 | self.0.next() 659 | } 660 | fn size_hint(&self) -> (usize, Option) { 661 | self.0.size_hint() 662 | } 663 | } 664 | 665 | impl<'a, K, V> DoubleEndedIterator for IterMut<'a, K, V> { 666 | fn next_back(&mut self) -> Option<(&'a K, &'a mut V)> { 667 | self.0.next_back() 668 | } 669 | } 670 | 671 | impl<'a, K, V> ExactSizeIterator for IterMut<'a, K, V> { 672 | fn len(&self) -> usize { 673 | self.0.len() 674 | } 675 | } 676 | -------------------------------------------------------------------------------- /src/disk_cache.rs: -------------------------------------------------------------------------------- 1 | use std::boxed::Box; 2 | use std::ffi::OsStr; 3 | use std::ffi::OsString; 4 | use std::fs::File; 5 | use std::fs::{self}; 6 | use std::hash::BuildHasher; 7 | use std::io; 8 | use std::io::prelude::*; 9 | use std::path::Path; 10 | use std::path::PathBuf; 11 | 12 | use filetime::set_file_times; 13 | use filetime::FileTime; 14 | use ritelinked::DefaultHashBuilder; 15 | use walkdir::WalkDir; 16 | 17 | use crate::FileSize; 18 | use crate::{Cache, LruCache}; 19 | 20 | /// Return an iterator of `(path, size)` of files under `path` sorted by ascending last-modified 21 | /// time, such that the oldest modified file is returned first. 22 | fn get_all_files>(path: P) -> Box> { 23 | let mut files: Vec<_> = WalkDir::new(path.as_ref()) 24 | .into_iter() 25 | .filter_map(|e| { 26 | e.ok().and_then(|f| { 27 | // Only look at files 28 | if f.file_type().is_file() { 29 | // Get the last-modified time, size, and the full path. 30 | f.metadata().ok().and_then(|m| { 31 | m.modified().ok().map(|mtime| (mtime, f.path().to_owned(), m.len())) 32 | }) 33 | } else { 34 | None 35 | } 36 | }) 37 | }) 38 | .collect(); 39 | // Sort by last-modified-time, so oldest file first. 40 | files.sort_by_key(|k| k.0); 41 | Box::new(files.into_iter().map(|(_mtime, path, size)| (path, size))) 42 | } 43 | 44 | /// An LRU cache of files on disk. 45 | pub type LruDiskCache = DiskCache>; 46 | 47 | /// An basic disk cache of files on disk. 48 | pub struct DiskCache 49 | where 50 | C: Cache, 51 | { 52 | hash_builder: S, 53 | cache: C, 54 | root: PathBuf, 55 | } 56 | 57 | /// Trait objects can't be bounded by more than one non-builtin trait. 58 | pub trait ReadSeek: Read + Seek + Send {} 59 | 60 | impl ReadSeek for T {} 61 | 62 | enum AddFile<'a> { 63 | AbsPath(PathBuf), 64 | RelPath(&'a OsStr), 65 | } 66 | 67 | impl DiskCache 68 | where 69 | C: Cache, 70 | { 71 | /// Create an `LruDiskCache` that stores files in `path`, limited to `size` bytes. 72 | /// 73 | /// Existing files in `path` will be stored with their last-modified time from the filesystem 74 | /// used as the order for the recency of their use. Any files that are individually larger 75 | /// than `size` bytes will be removed. 76 | /// 77 | /// The cache is not observant of changes to files under `path` from external sources, it 78 | /// expects to have sole maintence of the contents. 79 | pub fn new(path: T, size: u64) -> Result 80 | where 81 | PathBuf: From, 82 | { 83 | DiskCache { 84 | hash_builder: DefaultHashBuilder::new(), 85 | cache: C::with_meter_and_hasher(size, FileSize, DefaultHashBuilder::new()), 86 | root: PathBuf::from(path), 87 | } 88 | .init() 89 | } 90 | } 91 | 92 | impl DiskCache 93 | where 94 | C: Cache, 95 | S: BuildHasher + Clone, 96 | { 97 | /// Create an `LruDiskCache` that stores files in `path`, limited to `size` bytes. 98 | /// 99 | /// Existing files in `path` will be stored with their last-modified time from the filesystem 100 | /// used as the order for the recency of their use. Any files that are individually larger 101 | /// than `size` bytes will be removed. 102 | /// 103 | /// The cache is not observant of changes to files under `path` from external sources, it 104 | /// expects to have sole maintence of the contents. 105 | pub fn new_with_hasher(path: T, size: u64, hash_builder: S) -> Result 106 | where 107 | PathBuf: From, 108 | { 109 | DiskCache { 110 | hash_builder: hash_builder.clone(), 111 | cache: C::with_meter_and_hasher(size, FileSize, hash_builder), 112 | root: PathBuf::from(path), 113 | } 114 | .init() 115 | } 116 | 117 | /// Return the current size of all the files in the cache. 118 | pub fn size(&self) -> u64 { 119 | self.cache.size() 120 | } 121 | 122 | /// Return the count of entries in the cache. 123 | pub fn len(&self) -> usize { 124 | self.cache.len() 125 | } 126 | 127 | pub fn is_empty(&self) -> bool { 128 | self.cache.len() == 0 129 | } 130 | 131 | /// Return the maximum size of the cache. 132 | pub fn capacity(&self) -> u64 { 133 | self.cache.capacity() 134 | } 135 | 136 | /// Return the path in which the cache is stored. 137 | pub fn path(&self) -> &Path { 138 | self.root.as_path() 139 | } 140 | 141 | /// Return the path that `key` would be stored at. 142 | fn rel_to_abs_path>(&self, rel_path: K) -> PathBuf { 143 | self.root.join(rel_path) 144 | } 145 | 146 | /// Scan `self.root` for existing files and store them. 147 | fn init(mut self) -> Result { 148 | fs::create_dir_all(&self.root)?; 149 | for (file, size) in get_all_files(&self.root) { 150 | if !self.can_store(size) { 151 | fs::remove_file(file).unwrap_or_else(|e| { 152 | error!( 153 | "Error removing file `{}` which is too large for the cache ({} bytes)", 154 | e, size 155 | ) 156 | }); 157 | } else { 158 | self.add_file(AddFile::AbsPath(file), size) 159 | .unwrap_or_else(|e| error!("Error adding file: {}", e)); 160 | } 161 | } 162 | Ok(self) 163 | } 164 | 165 | /// Returns `true` if the disk cache can store a file of `size` bytes. 166 | pub fn can_store(&self, size: u64) -> bool { 167 | size <= self.cache.capacity() as u64 168 | } 169 | 170 | /// Add the file at `path` of size `size` to the cache. 171 | fn add_file(&mut self, addfile_path: AddFile<'_>, size: u64) -> Result<()> { 172 | if !self.can_store(size) { 173 | return Err(Error::FileTooLarge); 174 | } 175 | let rel_path = match addfile_path { 176 | AddFile::AbsPath(ref p) => p.strip_prefix(&self.root).expect("Bad path?").as_os_str(), 177 | AddFile::RelPath(p) => p, 178 | }; 179 | //TODO: ideally LruCache::put would give us back the entries it had to remove. 180 | while self.cache.size() as u64 + size > self.cache.capacity() as u64 { 181 | let (rel_path, _) = self.cache.pop_by_policy().expect("Unexpectedly empty cache!"); 182 | let remove_path = self.rel_to_abs_path(rel_path); 183 | //TODO: check that files are removable during `init`, so that this is only 184 | // due to outside interference. 185 | fs::remove_file(&remove_path).unwrap_or_else(|e| { 186 | panic!("Error removing file from cache: `{:?}`: {}", remove_path, e) 187 | }); 188 | } 189 | self.cache.put(rel_path.to_owned(), size); 190 | Ok(()) 191 | } 192 | 193 | fn insert_by, F: FnOnce(&Path) -> io::Result<()>>( 194 | &mut self, 195 | key: K, 196 | size: Option, 197 | by: F, 198 | ) -> Result<()> { 199 | if let Some(size) = size { 200 | if !self.can_store(size) { 201 | return Err(Error::FileTooLarge); 202 | } 203 | } 204 | let rel_path = key.as_ref(); 205 | let path = self.rel_to_abs_path(rel_path); 206 | fs::create_dir_all(path.parent().expect("Bad path?"))?; 207 | by(&path)?; 208 | let size = match size { 209 | Some(size) => size, 210 | None => fs::metadata(path)?.len(), 211 | }; 212 | self.add_file(AddFile::RelPath(rel_path), size).map_err(|e| { 213 | error!("Failed to insert file `{}`: {}", rel_path.to_string_lossy(), e); 214 | fs::remove_file(&self.rel_to_abs_path(rel_path)) 215 | .expect("Failed to remove file we just created!"); 216 | e 217 | }) 218 | } 219 | 220 | /// Add a file by calling `with` with the open `File` corresponding to the cache at path `key`. 221 | pub fn insert_with, F: FnOnce(File) -> io::Result<()>>( 222 | &mut self, 223 | key: K, 224 | with: F, 225 | ) -> Result<()> { 226 | self.insert_by(key, None, |path| with(File::create(&path)?)) 227 | } 228 | 229 | /// Add a file with `bytes` as its contents to the cache at path `key`. 230 | pub fn insert_bytes>(&mut self, key: K, bytes: &[u8]) -> Result<()> { 231 | self.insert_by(key, Some(bytes.len() as u64), |path| { 232 | let mut f = File::create(&path)?; 233 | f.write_all(bytes)?; 234 | Ok(()) 235 | }) 236 | } 237 | 238 | /// Add an existing file at `path` to the cache at path `key`. 239 | pub fn insert_file, P: AsRef>(&mut self, key: K, path: P) -> Result<()> { 240 | let size = fs::metadata(path.as_ref())?.len(); 241 | self.insert_by(key, Some(size), |new_path| { 242 | fs::rename(path.as_ref(), new_path).or_else(|_| { 243 | warn!("fs::rename failed, falling back to copy!"); 244 | fs::copy(path.as_ref(), new_path)?; 245 | fs::remove_file(path.as_ref()).unwrap_or_else(|e| { 246 | error!("Failed to remove original file in insert_file: {}", e) 247 | }); 248 | Ok(()) 249 | }) 250 | }) 251 | } 252 | 253 | /// Return `true` if a file with path `key` is in the cache. 254 | pub fn contains_key>(&self, key: K) -> bool { 255 | self.cache.contains(key.as_ref()) 256 | } 257 | 258 | /// Get an opened `File` for `key`, if one exists and can be opened. Updates the LRU state 259 | /// of the file if present. Avoid using this method if at all possible, prefer `.get`. 260 | pub fn get_file>(&mut self, key: K) -> Result { 261 | let rel_path = key.as_ref(); 262 | let path = self.rel_to_abs_path(rel_path); 263 | self.cache.get(rel_path).ok_or(Error::FileNotInCache).and_then(|_| { 264 | let t = FileTime::now(); 265 | set_file_times(&path, t, t)?; 266 | File::open(path).map_err(Into::into) 267 | }) 268 | } 269 | 270 | /// Get an opened readable and seekable handle to the file at `key`, if one exists and can 271 | /// be opened. Updates the LRU state of the file if present. 272 | pub fn get>(&mut self, key: K) -> Result> { 273 | self.get_file(key).map(|f| Box::new(f) as Box) 274 | } 275 | 276 | /// Remove the given key from the cache. 277 | pub fn remove>(&mut self, key: K) -> Result<()> { 278 | match self.cache.pop(key.as_ref()) { 279 | Some(_) => { 280 | let path = self.rel_to_abs_path(key.as_ref()); 281 | fs::remove_file(&path).map_err(|e| { 282 | error!("Error removing file from cache: `{:?}`: {}", path, e); 283 | Into::into(e) 284 | }) 285 | } 286 | None => Ok(()), 287 | } 288 | } 289 | } 290 | 291 | pub mod result { 292 | use std::error::Error as StdError; 293 | use std::fmt; 294 | use std::io; 295 | 296 | /// Errors returned by this crate. 297 | #[derive(Debug)] 298 | pub enum Error { 299 | /// The file was too large to fit in the cache. 300 | FileTooLarge, 301 | /// The file was not in the cache. 302 | FileNotInCache, 303 | /// An IO Error occurred. 304 | Io(io::Error), 305 | } 306 | 307 | impl fmt::Display for Error { 308 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 309 | match self { 310 | Error::FileTooLarge => write!(f, "File too large"), 311 | Error::FileNotInCache => write!(f, "File not in cache"), 312 | Error::Io(ref e) => write!(f, "{}", e), 313 | } 314 | } 315 | } 316 | 317 | impl StdError for Error { 318 | fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { 319 | match self { 320 | Error::FileTooLarge => None, 321 | Error::FileNotInCache => None, 322 | Error::Io(ref e) => Some(e), 323 | } 324 | } 325 | } 326 | 327 | impl From for Error { 328 | fn from(e: io::Error) -> Error { 329 | Error::Io(e) 330 | } 331 | } 332 | 333 | /// A convenience `Result` type 334 | pub type Result = std::result::Result; 335 | } 336 | 337 | use result::*; 338 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate log; 3 | #[cfg(feature = "heapsize")] 4 | extern crate heapsize_; 5 | 6 | mod cache; 7 | #[allow(dead_code)] 8 | mod disk_cache; 9 | mod meter; 10 | 11 | pub use cache::lru::LruCache; 12 | pub use cache::Cache; 13 | pub use disk_cache::result::Error as DiskCacheError; 14 | pub use disk_cache::result::Result as DiskCacheResult; 15 | pub use disk_cache::DiskCache; 16 | pub use disk_cache::LruDiskCache; 17 | pub use meter::count_meter::Count; 18 | pub use meter::count_meter::CountableMeter; 19 | pub use meter::file_meter::FileSize; 20 | #[cfg(feature = "heapsize")] 21 | pub use meter::heap_meter::HeapSize; 22 | pub use meter::Meter; 23 | -------------------------------------------------------------------------------- /src/meter.rs: -------------------------------------------------------------------------------- 1 | pub mod count_meter; 2 | pub mod file_meter; 3 | #[cfg(feature = "heapsize")] 4 | pub mod heap_meter; 5 | 6 | use std::borrow::Borrow; 7 | 8 | /// A trait for measuring the size of a cache entry. 9 | /// 10 | /// If you implement this trait, you should use `usize` as the `Measure` type, otherwise you will 11 | /// also have to implement [`CountableMeter`][countablemeter]. 12 | /// 13 | /// [countablemeter]: trait.Meter.html 14 | pub trait Meter { 15 | /// The type used to store measurements. 16 | type Measure: Default + Copy; 17 | /// Calculate the size of `key` and `value`. 18 | fn measure(&self, key: &Q, value: &V) -> Self::Measure 19 | where 20 | K: Borrow; 21 | } 22 | -------------------------------------------------------------------------------- /src/meter/count_meter.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Borrow; 2 | 3 | use super::Meter; 4 | 5 | /// Size limit based on a simple count of cache items. 6 | pub struct Count; 7 | 8 | impl Meter for Count { 9 | /// Don't store anything, the measurement can be derived from the map. 10 | type Measure = (); 11 | 12 | /// Don't actually count anything either. 13 | fn measure(&self, _: &Q, _: &V) 14 | where 15 | K: Borrow, 16 | { 17 | } 18 | } 19 | 20 | /// A trait to allow the default `Count` measurement to not store an 21 | /// extraneous counter. 22 | pub trait CountableMeter: Meter { 23 | /// Add `amount` to `current` and return the sum. 24 | fn add(&self, current: Self::Measure, amount: Self::Measure) -> Self::Measure; 25 | /// Subtract `amount` from `current` and return the difference. 26 | fn sub(&self, current: Self::Measure, amount: Self::Measure) -> Self::Measure; 27 | /// Return `current` as a `usize` if possible, otherwise return `None`. 28 | /// 29 | /// If this method returns `None` the cache will use the number of cache entries as 30 | /// its size. 31 | fn size(&self, current: Self::Measure) -> Option; 32 | } 33 | 34 | /// `Count` is all no-ops, the number of entries in the map is the size. 35 | impl> CountableMeter for T 36 | where 37 | T: CountableMeterWithMeasure>::Measure>, 38 | { 39 | fn add(&self, current: Self::Measure, amount: Self::Measure) -> Self::Measure { 40 | CountableMeterWithMeasure::meter_add(self, current, amount) 41 | } 42 | fn sub(&self, current: Self::Measure, amount: Self::Measure) -> Self::Measure { 43 | CountableMeterWithMeasure::meter_sub(self, current, amount) 44 | } 45 | fn size(&self, current: Self::Measure) -> Option { 46 | CountableMeterWithMeasure::meter_size(self, current) 47 | } 48 | } 49 | 50 | pub trait CountableMeterWithMeasure { 51 | /// Add `amount` to `current` and return the sum. 52 | fn meter_add(&self, current: M, amount: M) -> M; 53 | /// Subtract `amount` from `current` and return the difference. 54 | fn meter_sub(&self, current: M, amount: M) -> M; 55 | /// Return `current` as a `usize` if possible, otherwise return `None`. 56 | /// 57 | /// If this method returns `None` the cache will use the number of cache entries as 58 | /// its size. 59 | fn meter_size(&self, current: M) -> Option; 60 | } 61 | 62 | /// For any other `Meter` with `Measure=usize`, just do the simple math. 63 | impl CountableMeterWithMeasure for T 64 | where 65 | T: Meter, 66 | { 67 | fn meter_add(&self, current: usize, amount: usize) -> usize { 68 | current + amount 69 | } 70 | fn meter_sub(&self, current: usize, amount: usize) -> usize { 71 | current - amount 72 | } 73 | fn meter_size(&self, current: usize) -> Option { 74 | Some(current as u64) 75 | } 76 | } 77 | 78 | impl CountableMeterWithMeasure for Count { 79 | fn meter_add(&self, _current: (), _amount: ()) {} 80 | fn meter_sub(&self, _current: (), _amount: ()) {} 81 | fn meter_size(&self, _current: ()) -> Option { 82 | None 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/meter/file_meter.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Borrow; 2 | 3 | use super::Meter; 4 | 5 | pub struct FileSize; 6 | 7 | /// Given a tuple of (path, filesize), use the filesize for measurement. 8 | impl Meter for FileSize { 9 | type Measure = usize; 10 | fn measure(&self, _: &Q, v: &u64) -> usize 11 | where 12 | K: Borrow, 13 | { 14 | *v as usize 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/meter/heap_meter.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Borrow; 2 | 3 | use heapsize_::HeapSizeOf; 4 | 5 | use super::Meter; 6 | 7 | /// Size limit based on the heap size of each cache item. 8 | /// 9 | /// Requires cache entries that implement [`HeapSizeOf`][1]. 10 | /// 11 | /// [1]: https://doc.servo.org/heapsize/trait.HeapSizeOf.html 12 | pub struct HeapSize; 13 | 14 | impl Meter for HeapSize { 15 | type Measure = usize; 16 | 17 | fn measure(&self, _: &Q, item: &V) -> usize 18 | where 19 | K: Borrow, 20 | { 21 | item.heap_size_of_children() + ::std::mem::size_of::() 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/disk_cache_test.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::fs::{self}; 3 | use std::io::Read; 4 | use std::io::Write; 5 | use std::io::{self}; 6 | use std::path::Path; 7 | use std::path::PathBuf; 8 | 9 | use filetime::set_file_times; 10 | use filetime::FileTime; 11 | use tempfile::TempDir; 12 | 13 | use ritecache::DiskCacheError; 14 | use ritecache::LruDiskCache; 15 | 16 | struct TestFixture { 17 | /// Temp directory. 18 | pub tempdir: TempDir, 19 | } 20 | 21 | fn create_file, F: FnOnce(File) -> io::Result<()>>( 22 | dir: &Path, 23 | path: T, 24 | fill_contents: F, 25 | ) -> io::Result { 26 | let b = dir.join(path); 27 | fs::create_dir_all(b.parent().unwrap())?; 28 | let f = fs::File::create(&b)?; 29 | fill_contents(f)?; 30 | b.canonicalize() 31 | } 32 | 33 | /// Set the last modified time of `path` backwards by `seconds` seconds. 34 | fn set_mtime_back>(path: T, seconds: usize) { 35 | let m = fs::metadata(path.as_ref()).unwrap(); 36 | let t = FileTime::from_last_modification_time(&m); 37 | let t = FileTime::from_unix_time(t.unix_seconds() - seconds as i64, t.nanoseconds()); 38 | set_file_times(path, t, t).unwrap(); 39 | } 40 | 41 | fn read_all(r: &mut R) -> io::Result> { 42 | let mut v = vec![]; 43 | r.read_to_end(&mut v)?; 44 | Ok(v) 45 | } 46 | 47 | impl TestFixture { 48 | pub fn new() -> TestFixture { 49 | TestFixture { 50 | tempdir: tempfile::Builder::new().prefix("lru-disk-cache-test").tempdir().unwrap(), 51 | } 52 | } 53 | 54 | pub fn tmp(&self) -> &Path { 55 | self.tempdir.path() 56 | } 57 | 58 | pub fn create_file>(&self, path: T, size: usize) -> PathBuf { 59 | create_file(self.tempdir.path(), path, |mut f| f.write_all(&vec![0; size])).unwrap() 60 | } 61 | } 62 | 63 | #[test] 64 | fn test_empty_dir() { 65 | let f = TestFixture::new(); 66 | LruDiskCache::new(f.tmp(), 1024).unwrap(); 67 | } 68 | 69 | #[test] 70 | fn test_missing_root() { 71 | let f = TestFixture::new(); 72 | LruDiskCache::new(f.tmp().join("not-here"), 1024).unwrap(); 73 | } 74 | 75 | #[test] 76 | fn test_some_existing_files() { 77 | let f = TestFixture::new(); 78 | f.create_file("file1", 10); 79 | f.create_file("file2", 10); 80 | let c = LruDiskCache::new(f.tmp(), 20).unwrap(); 81 | assert_eq!(c.size(), 20); 82 | assert_eq!(c.len(), 2); 83 | } 84 | 85 | #[test] 86 | fn test_existing_file_too_large() { 87 | let f = TestFixture::new(); 88 | // Create files explicitly in the past. 89 | set_mtime_back(f.create_file("file1", 10), 10); 90 | set_mtime_back(f.create_file("file2", 10), 5); 91 | let c = LruDiskCache::new(f.tmp(), 15).unwrap(); 92 | assert_eq!(c.size(), 10); 93 | assert_eq!(c.len(), 1); 94 | assert!(!c.contains_key("file1")); 95 | assert!(c.contains_key("file2")); 96 | } 97 | 98 | #[test] 99 | fn test_existing_files_lru_mtime() { 100 | let f = TestFixture::new(); 101 | // Create files explicitly in the past. 102 | set_mtime_back(f.create_file("file1", 10), 5); 103 | set_mtime_back(f.create_file("file2", 10), 10); 104 | let mut c = LruDiskCache::new(f.tmp(), 25).unwrap(); 105 | assert_eq!(c.size(), 20); 106 | c.insert_bytes("file3", &[0; 10]).unwrap(); 107 | assert_eq!(c.size(), 20); 108 | // The oldest file on disk should have been removed. 109 | assert!(!c.contains_key("file2")); 110 | assert!(c.contains_key("file1")); 111 | } 112 | 113 | #[test] 114 | fn test_insert_bytes() { 115 | let f = TestFixture::new(); 116 | let mut c = LruDiskCache::new(f.tmp(), 25).unwrap(); 117 | c.insert_bytes("a/b/c", &[0; 10]).unwrap(); 118 | assert!(c.contains_key("a/b/c")); 119 | c.insert_bytes("a/b/d", &[0; 10]).unwrap(); 120 | assert_eq!(c.size(), 20); 121 | // Adding this third file should put the cache above the limit. 122 | c.insert_bytes("x/y/z", &[0; 10]).unwrap(); 123 | assert_eq!(c.size(), 20); 124 | // The least-recently-used file should have been removed. 125 | assert!(!c.contains_key("a/b/c")); 126 | assert!(!f.tmp().join("a/b/c").exists()); 127 | } 128 | 129 | #[test] 130 | fn test_insert_bytes_exact() { 131 | // Test that files adding up to exactly the size limit works. 132 | let f = TestFixture::new(); 133 | let mut c = LruDiskCache::new(f.tmp(), 20).unwrap(); 134 | c.insert_bytes("file1", &[1; 10]).unwrap(); 135 | c.insert_bytes("file2", &[2; 10]).unwrap(); 136 | assert_eq!(c.size(), 20); 137 | c.insert_bytes("file3", &[3; 10]).unwrap(); 138 | assert_eq!(c.size(), 20); 139 | assert!(!c.contains_key("file1")); 140 | } 141 | 142 | #[test] 143 | fn test_add_get_lru() { 144 | let f = TestFixture::new(); 145 | { 146 | let mut c = LruDiskCache::new(f.tmp(), 25).unwrap(); 147 | c.insert_bytes("file1", &[1; 10]).unwrap(); 148 | c.insert_bytes("file2", &[2; 10]).unwrap(); 149 | // Get the file to bump its LRU status. 150 | assert_eq!(read_all(&mut c.get("file1").unwrap()).unwrap(), vec![1u8; 10]); 151 | // Adding this third file should put the cache above the limit. 152 | c.insert_bytes("file3", &[3; 10]).unwrap(); 153 | assert_eq!(c.size(), 20); 154 | // The least-recently-used file should have been removed. 155 | assert!(!c.contains_key("file2")); 156 | } 157 | // Get rid of the cache, to test that the LRU persists on-disk as mtimes. 158 | // This is hacky, but mtime resolution on my mac with HFS+ is only 1 second, so we either 159 | // need to have a 1 second sleep in the test (boo) or adjust the mtimes back a bit so 160 | // that updating one file to the current time actually works to make it newer. 161 | set_mtime_back(f.tmp().join("file1"), 5); 162 | set_mtime_back(f.tmp().join("file3"), 5); 163 | { 164 | let mut c = LruDiskCache::new(f.tmp(), 25).unwrap(); 165 | // Bump file1 again. 166 | c.get("file1").unwrap(); 167 | } 168 | // Now check that the on-disk mtimes were updated and used. 169 | { 170 | let mut c = LruDiskCache::new(f.tmp(), 25).unwrap(); 171 | assert!(c.contains_key("file1")); 172 | assert!(c.contains_key("file3")); 173 | assert_eq!(c.size(), 20); 174 | // Add another file to bump out the least-recently-used. 175 | c.insert_bytes("file4", &[4; 10]).unwrap(); 176 | assert_eq!(c.size(), 20); 177 | assert!(!c.contains_key("file3")); 178 | assert!(c.contains_key("file1")); 179 | } 180 | } 181 | 182 | #[test] 183 | fn test_insert_bytes_too_large() { 184 | let f = TestFixture::new(); 185 | let mut c = LruDiskCache::new(f.tmp(), 1).unwrap(); 186 | match c.insert_bytes("a/b/c", &[0; 2]) { 187 | Err(DiskCacheError::FileTooLarge) => {} 188 | x => panic!("Unexpected result: {:?}", x), 189 | } 190 | } 191 | 192 | #[test] 193 | fn test_insert_file() { 194 | let f = TestFixture::new(); 195 | let p1 = f.create_file("file1", 10); 196 | let p2 = f.create_file("file2", 10); 197 | let p3 = f.create_file("file3", 10); 198 | let mut c = LruDiskCache::new(f.tmp().join("cache"), 25).unwrap(); 199 | c.insert_file("file1", &p1).unwrap(); 200 | assert_eq!(c.len(), 1); 201 | c.insert_file("file2", &p2).unwrap(); 202 | assert_eq!(c.len(), 2); 203 | // Get the file to bump its LRU status. 204 | assert_eq!(read_all(&mut c.get("file1").unwrap()).unwrap(), vec![0u8; 10]); 205 | // Adding this third file should put the cache above the limit. 206 | c.insert_file("file3", &p3).unwrap(); 207 | assert_eq!(c.len(), 2); 208 | assert_eq!(c.size(), 20); 209 | // The least-recently-used file should have been removed. 210 | assert!(!c.contains_key("file2")); 211 | assert!(!p1.exists()); 212 | assert!(!p2.exists()); 213 | assert!(!p3.exists()); 214 | } 215 | 216 | #[test] 217 | fn test_remove() { 218 | let f = TestFixture::new(); 219 | let p1 = f.create_file("file1", 10); 220 | let p2 = f.create_file("file2", 10); 221 | let p3 = f.create_file("file3", 10); 222 | let mut c = LruDiskCache::new(f.tmp().join("cache"), 25).unwrap(); 223 | c.insert_file("file1", &p1).unwrap(); 224 | c.insert_file("file2", &p2).unwrap(); 225 | c.remove("file1").unwrap(); 226 | c.insert_file("file3", &p3).unwrap(); 227 | assert_eq!(c.len(), 2); 228 | assert_eq!(c.size(), 20); 229 | 230 | // file1 should have been removed. 231 | assert!(!c.contains_key("file1")); 232 | assert!(!f.tmp().join("cache").join("file1").exists()); 233 | assert!(f.tmp().join("cache").join("file2").exists()); 234 | assert!(f.tmp().join("cache").join("file3").exists()); 235 | assert!(!p1.exists()); 236 | assert!(!p2.exists()); 237 | assert!(!p3.exists()); 238 | 239 | let p4 = f.create_file("file1", 10); 240 | c.insert_file("file1", &p4).unwrap(); 241 | assert_eq!(c.len(), 2); 242 | // file2 should have been removed. 243 | assert!(c.contains_key("file1")); 244 | assert!(!c.contains_key("file2")); 245 | assert!(!f.tmp().join("cache").join("file2").exists()); 246 | assert!(!p4.exists()); 247 | } 248 | -------------------------------------------------------------------------------- /tests/lru_test.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Borrow; 2 | 3 | use ritecache::Cache; 4 | use ritecache::LruCache; 5 | use ritecache::Meter; 6 | 7 | #[test] 8 | fn test_put_and_get() { 9 | let mut cache = LruCache::new(2); 10 | cache.put(1, 10); 11 | cache.put(2, 20); 12 | assert_eq!(cache.get_mut(&1), Some(&mut 10)); 13 | assert_eq!(cache.get_mut(&2), Some(&mut 20)); 14 | assert_eq!(cache.len(), 2); 15 | assert_eq!(cache.size(), 2); 16 | } 17 | 18 | #[test] 19 | fn test_put_update() { 20 | let mut cache = LruCache::new(1); 21 | cache.put("1", 10); 22 | cache.put("1", 19); 23 | assert_eq!(cache.get_mut("1"), Some(&mut 19)); 24 | assert_eq!(cache.len(), 1); 25 | } 26 | 27 | #[test] 28 | fn test_contains() { 29 | let mut cache = LruCache::new(1); 30 | cache.put("1", 10); 31 | assert!(cache.contains("1")); 32 | } 33 | 34 | #[test] 35 | fn test_expire_lru() { 36 | let mut cache = LruCache::new(2); 37 | cache.put("foo1", "bar1"); 38 | cache.put("foo2", "bar2"); 39 | cache.put("foo3", "bar3"); 40 | assert!(cache.get_mut("foo1").is_none()); 41 | cache.put("foo2", "bar2update"); 42 | cache.put("foo4", "bar4"); 43 | assert!(cache.get_mut("foo3").is_none()); 44 | } 45 | 46 | #[test] 47 | fn test_pop() { 48 | let mut cache = LruCache::new(2); 49 | cache.put(1, 10); 50 | cache.put(2, 20); 51 | assert_eq!(cache.len(), 2); 52 | let opt1 = cache.pop(&1); 53 | assert!(opt1.is_some()); 54 | assert_eq!(opt1.unwrap(), 10); 55 | assert!(cache.get_mut(&1).is_none()); 56 | assert_eq!(cache.len(), 1); 57 | } 58 | 59 | #[test] 60 | fn test_change_capacity() { 61 | let mut cache = LruCache::new(2); 62 | assert_eq!(cache.capacity(), 2); 63 | cache.put(1, 10); 64 | cache.put(2, 20); 65 | cache.set_capacity(1); 66 | assert!(cache.get_mut(&1).is_none()); 67 | assert_eq!(cache.capacity(), 1); 68 | } 69 | 70 | #[test] 71 | fn test_debug() { 72 | let mut cache = LruCache::new(3); 73 | cache.put(1, 10); 74 | cache.put(2, 20); 75 | cache.put(3, 30); 76 | assert_eq!(format!("{:?}", cache), "{3: 30, 2: 20, 1: 10}"); 77 | cache.put(2, 22); 78 | assert_eq!(format!("{:?}", cache), "{2: 22, 3: 30, 1: 10}"); 79 | cache.put(6, 60); 80 | assert_eq!(format!("{:?}", cache), "{6: 60, 2: 22, 3: 30}"); 81 | cache.get_mut(&3); 82 | assert_eq!(format!("{:?}", cache), "{3: 30, 6: 60, 2: 22}"); 83 | cache.set_capacity(2); 84 | assert_eq!(format!("{:?}", cache), "{3: 30, 6: 60}"); 85 | } 86 | 87 | #[test] 88 | fn test_remove() { 89 | let mut cache = LruCache::new(3); 90 | cache.put(1, 10); 91 | cache.put(2, 20); 92 | cache.put(3, 30); 93 | cache.put(4, 40); 94 | cache.put(5, 50); 95 | cache.pop(&3); 96 | cache.pop(&4); 97 | assert!(cache.get_mut(&3).is_none()); 98 | assert!(cache.get_mut(&4).is_none()); 99 | cache.put(6, 60); 100 | cache.put(7, 70); 101 | cache.put(8, 80); 102 | assert!(cache.get_mut(&5).is_none()); 103 | assert_eq!(cache.get_mut(&6), Some(&mut 60)); 104 | assert_eq!(cache.get_mut(&7), Some(&mut 70)); 105 | assert_eq!(cache.get_mut(&8), Some(&mut 80)); 106 | } 107 | 108 | #[test] 109 | fn test_clear() { 110 | let mut cache = LruCache::new(2); 111 | cache.put(1, 10); 112 | cache.put(2, 20); 113 | cache.clear(); 114 | assert!(cache.get_mut(&1).is_none()); 115 | assert!(cache.get_mut(&2).is_none()); 116 | assert_eq!(format!("{:?}", cache), "{}"); 117 | } 118 | 119 | #[test] 120 | fn test_iter() { 121 | let mut cache = LruCache::new(3); 122 | cache.put(1, 10); 123 | cache.put(2, 20); 124 | cache.put(3, 30); 125 | cache.put(4, 40); 126 | cache.put(5, 50); 127 | assert_eq!(cache.iter().collect::>(), [(&3, &30), (&4, &40), (&5, &50)]); 128 | assert_eq!(cache.iter_mut().collect::>(), [(&3, &mut 30), (&4, &mut 40), (&5, &mut 50)]); 129 | assert_eq!(cache.iter().rev().collect::>(), [(&5, &50), (&4, &40), (&3, &30)]); 130 | assert_eq!( 131 | cache.iter_mut().rev().collect::>(), 132 | [(&5, &mut 50), (&4, &mut 40), (&3, &mut 30)] 133 | ); 134 | } 135 | 136 | struct VecLen; 137 | 138 | impl Meter> for VecLen { 139 | type Measure = usize; 140 | fn measure(&self, _: &Q, v: &Vec) -> usize 141 | where 142 | K: Borrow, 143 | { 144 | v.len() 145 | } 146 | } 147 | 148 | #[test] 149 | fn test_metered_cache() { 150 | let mut cache = LruCache::with_meter(5, VecLen); 151 | cache.put("foo1", vec![1, 2]); 152 | assert_eq!(cache.size(), 2); 153 | cache.put("foo2", vec![3, 4]); 154 | cache.put("foo3", vec![5, 6]); 155 | assert_eq!(cache.size(), 4); 156 | assert!(!cache.contains("foo1")); 157 | cache.put("foo2", vec![7, 8]); 158 | cache.put("foo4", vec![9, 10]); 159 | assert_eq!(cache.size(), 4); 160 | assert!(!cache.contains("foo3")); 161 | assert_eq!(cache.get("foo2"), Some(&vec![7, 8])); 162 | } 163 | 164 | #[test] 165 | fn test_metered_cache_reinsert_larger() { 166 | let mut cache = LruCache::with_meter(5, VecLen); 167 | cache.put("foo1", vec![1, 2]); 168 | cache.put("foo2", vec![3, 4]); 169 | assert_eq!(cache.size(), 4); 170 | cache.put("foo2", vec![5, 6, 7, 8]); 171 | assert_eq!(cache.size(), 4); 172 | assert!(!cache.contains("foo1")); 173 | } 174 | 175 | #[test] 176 | fn test_metered_cache_oversize() { 177 | let mut cache = LruCache::with_meter(2, VecLen); 178 | cache.put("foo1", vec![1, 2]); 179 | cache.put("foo2", vec![3, 4, 5, 6]); 180 | assert_eq!(cache.size(), 0); 181 | assert!(!cache.contains("foo1")); 182 | assert!(!cache.contains("foo2")); 183 | } 184 | 185 | #[cfg(feature = "heapsize")] 186 | #[test] 187 | fn test_heapsize_cache() { 188 | use crate::HeapSize; 189 | 190 | let mut cache = LruCache::<&str, (u8, u8, u8), _, _>::with_meter(8, HeapSize); 191 | cache.put("foo1", (1, 2, 3)); 192 | cache.put("foo2", (4, 5, 6)); 193 | cache.put("foo3", (7, 8, 9)); 194 | assert!(!cache.contains("foo1")); 195 | cache.put("foo2", (10, 11, 12)); 196 | cache.put("foo4", (13, 14, 15)); 197 | assert!(!cache.contains("foo3")); 198 | } 199 | --------------------------------------------------------------------------------