[shared_preferences_linux] Add support for Linux (#2836)

Adds shared_preferences support for Linux.

Part of flutter/flutter#41720
diff --git a/packages/shared_preferences/shared_preferences_linux/.gitignore b/packages/shared_preferences/shared_preferences_linux/.gitignore
new file mode 100644
index 0000000..e9dc58d
--- /dev/null
+++ b/packages/shared_preferences/shared_preferences_linux/.gitignore
@@ -0,0 +1,7 @@
+.DS_Store
+.dart_tool/
+
+.packages
+.pub/
+
+build/
diff --git a/packages/shared_preferences/shared_preferences_linux/.metadata b/packages/shared_preferences/shared_preferences_linux/.metadata
new file mode 100644
index 0000000..9615744
--- /dev/null
+++ b/packages/shared_preferences/shared_preferences_linux/.metadata
@@ -0,0 +1,10 @@
+# This file tracks properties of this Flutter project.
+# Used by Flutter tool to assess capabilities and perform upgrades etc.
+#
+# This file should be version controlled and should not be manually edited.
+
+version:
+  revision: e491544588e8d34fdf31d5f840b4649850ef167a
+  channel: master
+
+project_type: plugin
diff --git a/packages/shared_preferences/shared_preferences_linux/CHANGELOG.md b/packages/shared_preferences/shared_preferences_linux/CHANGELOG.md
new file mode 100644
index 0000000..1169480
--- /dev/null
+++ b/packages/shared_preferences/shared_preferences_linux/CHANGELOG.md
@@ -0,0 +1,2 @@
+## 0.0.1
+* Initial release to support shared_preferences on Linux.
diff --git a/packages/shared_preferences/shared_preferences_linux/LICENSE b/packages/shared_preferences/shared_preferences_linux/LICENSE
new file mode 100644
index 0000000..0c91662
--- /dev/null
+++ b/packages/shared_preferences/shared_preferences_linux/LICENSE
@@ -0,0 +1,27 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//    * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//    * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//    * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/packages/shared_preferences/shared_preferences_linux/README.md b/packages/shared_preferences/shared_preferences_linux/README.md
new file mode 100644
index 0000000..1894f50
--- /dev/null
+++ b/packages/shared_preferences/shared_preferences_linux/README.md
@@ -0,0 +1,22 @@
+# shared_preferences_linux
+
+The Linux implementation of [`shared_preferences`][1].
+
+## Usage
+
+### Import the package
+
+This package is an unendorsed Linux implementation of `shared_preferences`.
+
+In order to use this now, you'll need to depend on `shared_preferences_linux`.
+When this package is endorsed it will be automatically used by the `shared_preferences` package and you can switch to that API.
+
+```yaml
+...
+dependencies:
+  ...
+  shared_preferences_linux: ^0.0.1
+  ...
+```
+
+[1]: ../
diff --git a/packages/shared_preferences/shared_preferences_linux/example/.gitignore b/packages/shared_preferences/shared_preferences_linux/example/.gitignore
new file mode 100644
index 0000000..1ba9c33
--- /dev/null
+++ b/packages/shared_preferences/shared_preferences_linux/example/.gitignore
@@ -0,0 +1,43 @@
+# Miscellaneous
+*.class
+*.log
+*.pyc
+*.swp
+.DS_Store
+.atom/
+.buildlog/
+.history
+.svn/
+
+# IntelliJ related
+*.iml
+*.ipr
+*.iws
+.idea/
+
+# The .vscode folder contains launch configuration and tasks you configure in
+# VS Code which you may wish to be included in version control, so this line
+# is commented out by default.
+#.vscode/
+
+# Flutter/Dart/Pub related
+**/doc/api/
+.dart_tool/
+.flutter-plugins
+.flutter-plugins-dependencies
+.packages
+.pub-cache/
+.pub/
+/build/
+
+# Web related
+lib/generated_plugin_registrant.dart
+
+# Symbolication related
+app.*.symbols
+
+# Obfuscation related
+app.*.map.json
+
+# Exceptions to above rules.
+!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
diff --git a/packages/shared_preferences/shared_preferences_linux/example/.metadata b/packages/shared_preferences/shared_preferences_linux/example/.metadata
new file mode 100644
index 0000000..c0bc9a9
--- /dev/null
+++ b/packages/shared_preferences/shared_preferences_linux/example/.metadata
@@ -0,0 +1,10 @@
+# This file tracks properties of this Flutter project.
+# Used by Flutter tool to assess capabilities and perform upgrades etc.
+#
+# This file should be version controlled and should not be manually edited.
+
+version:
+  revision: e491544588e8d34fdf31d5f840b4649850ef167a
+  channel: master
+
+project_type: app
diff --git a/packages/shared_preferences/shared_preferences_linux/example/README.md b/packages/shared_preferences/shared_preferences_linux/example/README.md
new file mode 100644
index 0000000..9d3bf1f
--- /dev/null
+++ b/packages/shared_preferences/shared_preferences_linux/example/README.md
@@ -0,0 +1,8 @@
+# shared_preferences_example
+
+Demonstrates how to use the shared_preferences plugin.
+
+## Getting Started
+
+For help getting started with Flutter, view our online
+[documentation](http://flutter.io/).
diff --git a/packages/shared_preferences/shared_preferences_linux/example/lib/main.dart b/packages/shared_preferences/shared_preferences_linux/example/lib/main.dart
new file mode 100644
index 0000000..ceacf2f
--- /dev/null
+++ b/packages/shared_preferences/shared_preferences_linux/example/lib/main.dart
@@ -0,0 +1,87 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// ignore_for_file: public_member_api_docs
+
+import 'dart:async';
+
+import 'package:flutter/material.dart';
+import 'package:shared_preferences_linux/shared_preferences_linux.dart';
+
+void main() {
+  runApp(MyApp());
+}
+
+class MyApp extends StatelessWidget {
+  @override
+  Widget build(BuildContext context) {
+    return MaterialApp(
+      title: 'SharedPreferences Demo',
+      home: SharedPreferencesDemo(),
+    );
+  }
+}
+
+class SharedPreferencesDemo extends StatefulWidget {
+  SharedPreferencesDemo({Key key}) : super(key: key);
+
+  @override
+  SharedPreferencesDemoState createState() => SharedPreferencesDemoState();
+}
+
+class SharedPreferencesDemoState extends State<SharedPreferencesDemo> {
+  final prefs = SharedPreferencesLinux.instance;
+  Future<int> _counter;
+
+  Future<void> _incrementCounter() async {
+    final values = await prefs.getAll();
+    final int counter = (values['counter'] as int ?? 0) + 1;
+
+    setState(() {
+      _counter = prefs.setValue(null, "counter", counter).then((bool success) {
+        return counter;
+      });
+    });
+  }
+
+  @override
+  void initState() {
+    super.initState();
+    _counter = prefs.getAll().then((Map<String, Object> values) {
+      return (values['counter'] ?? 0);
+    });
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return Scaffold(
+      appBar: AppBar(
+        title: const Text("SharedPreferences Demo"),
+      ),
+      body: Center(
+          child: FutureBuilder<int>(
+              future: _counter,
+              builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
+                switch (snapshot.connectionState) {
+                  case ConnectionState.waiting:
+                    return const CircularProgressIndicator();
+                  default:
+                    if (snapshot.hasError) {
+                      return Text('Error: ${snapshot.error}');
+                    } else {
+                      return Text(
+                        'Button tapped ${snapshot.data} time${snapshot.data == 1 ? '' : 's'}.\n\n'
+                        'This should persist across restarts.',
+                      );
+                    }
+                }
+              })),
+      floatingActionButton: FloatingActionButton(
+        onPressed: _incrementCounter,
+        tooltip: 'Increment',
+        child: const Icon(Icons.add),
+      ),
+    );
+  }
+}
diff --git a/packages/shared_preferences/shared_preferences_linux/example/pubspec.yaml b/packages/shared_preferences/shared_preferences_linux/example/pubspec.yaml
new file mode 100644
index 0000000..1c06240
--- /dev/null
+++ b/packages/shared_preferences/shared_preferences_linux/example/pubspec.yaml
@@ -0,0 +1,15 @@
+name: shared_preferences_linux_example
+description: Demonstrates how to use the shared_preferences_linux plugin.
+
+dependencies:
+  flutter:
+    sdk: flutter
+  shared_preferences_linux: ^0.1.0
+
+dependency_overrides:
+  shared_preferences_linux:
+    path: ../
+
+flutter:
+  uses-material-design: true
+
diff --git a/packages/shared_preferences/shared_preferences_linux/lib/shared_preferences_linux.dart b/packages/shared_preferences/shared_preferences_linux/lib/shared_preferences_linux.dart
new file mode 100644
index 0000000..dc93100
--- /dev/null
+++ b/packages/shared_preferences/shared_preferences_linux/lib/shared_preferences_linux.dart
@@ -0,0 +1,96 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:async';
+import 'dart:convert' show json;
+import 'package:file/file.dart';
+import 'package:file/local.dart';
+import 'package:meta/meta.dart';
+import 'package:path/path.dart' as path;
+import 'package:path_provider/path_provider.dart';
+import 'package:shared_preferences_platform_interface/shared_preferences_platform_interface.dart';
+
+/// The Linux implementation of [SharedPreferencesStorePlatform].
+///
+/// This class implements the `package:shared_preferences` functionality for Linux.
+class SharedPreferencesLinux extends SharedPreferencesStorePlatform {
+  /// The default instance of [SharedPreferencesLinux] to use.
+  static SharedPreferencesLinux instance = SharedPreferencesLinux();
+
+  /// Local copy of preferences
+  Map<String, Object> _cachedPreferences;
+
+  /// File system used to store to disk. Exposed for testing only.
+  @visibleForTesting
+  FileSystem fs = LocalFileSystem();
+
+  /// Gets the file where the preferences are stored.
+  Future<File> _getLocalDataFile() async {
+    var directory = await getApplicationSupportDirectory();
+    var filePath = path.join(directory.path, 'shared_preferences.json');
+    return fs.file(filePath);
+  }
+
+  /// Gets the preferences from the stored file. Once read, the preferences are
+  /// maintained in memory.
+  Future<Map<String, Object>> _readPreferences() async {
+    if (_cachedPreferences != null) {
+      return _cachedPreferences;
+    }
+
+    _cachedPreferences = {};
+    var localDataFile = await _getLocalDataFile();
+    if (localDataFile.existsSync()) {
+      String stringMap = localDataFile.readAsStringSync();
+      if (stringMap.isNotEmpty) {
+        _cachedPreferences = json.decode(stringMap) as Map<String, Object>;
+      }
+    }
+
+    return _cachedPreferences;
+  }
+
+  /// Writes the cached preferences to disk. Returns [true] if the operation
+  /// succeeded.
+  Future<bool> _writePreferences(Map<String, Object> preferences) async {
+    try {
+      var localDataFile = await _getLocalDataFile();
+      if (!localDataFile.existsSync()) {
+        localDataFile.createSync(recursive: true);
+      }
+      var stringMap = json.encode(preferences);
+      localDataFile.writeAsStringSync(stringMap);
+    } catch (e) {
+      print("Error saving preferences to disk: $e");
+      return false;
+    }
+    return true;
+  }
+
+  @override
+  Future<bool> clear() async {
+    var preferences = await _readPreferences();
+    preferences.clear();
+    return _writePreferences(preferences);
+  }
+
+  @override
+  Future<Map<String, Object>> getAll() async {
+    return _readPreferences();
+  }
+
+  @override
+  Future<bool> remove(String key) async {
+    var preferences = await _readPreferences();
+    preferences.remove(key);
+    return _writePreferences(preferences);
+  }
+
+  @override
+  Future<bool> setValue(String valueType, String key, Object value) async {
+    var preferences = await _readPreferences();
+    preferences[key] = value;
+    return _writePreferences(preferences);
+  }
+}
diff --git a/packages/shared_preferences/shared_preferences_linux/pubspec.yaml b/packages/shared_preferences/shared_preferences_linux/pubspec.yaml
new file mode 100644
index 0000000..2539b93
--- /dev/null
+++ b/packages/shared_preferences/shared_preferences_linux/pubspec.yaml
@@ -0,0 +1,29 @@
+name: shared_preferences_linux
+description: Linux implementation of the shared_preferences plugin
+version: 0.0.1
+homepage: https://github.com/flutter/plugins/tree/master/packages/shared_preferences/shared_preferences_linux
+
+flutter:
+  plugin:
+    platforms:
+      linux:
+        dartPluginClass: SharedPreferencesLinux
+        pluginClass: none
+
+environment:
+  sdk: ">=2.1.0 <3.0.0"
+  flutter: ">=1.12.8 <2.0.0"
+
+dependencies:
+  file: ^5.1.0
+  flutter:
+    sdk: flutter
+  meta: ^1.0.4
+  path: ^1.6.4
+  path_provider: ^1.6.11
+  shared_preferences_platform_interface: ^1.0.0
+
+dev_dependencies:
+  flutter_test:
+    sdk: flutter
+  pedantic: ^1.8.0
diff --git a/packages/shared_preferences/shared_preferences_linux/test/shared_preferences_linux_test.dart b/packages/shared_preferences/shared_preferences_linux/test/shared_preferences_linux_test.dart
new file mode 100644
index 0000000..8a794b1
--- /dev/null
+++ b/packages/shared_preferences/shared_preferences_linux/test/shared_preferences_linux_test.dart
@@ -0,0 +1,74 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+import 'package:file/memory.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:path/path.dart' as path;
+import 'package:path_provider/path_provider.dart';
+import 'package:shared_preferences_linux/shared_preferences_linux.dart';
+
+MemoryFileSystem fs = MemoryFileSystem.test();
+
+void main() {
+  setUp(() {});
+
+  tearDown(() {});
+
+  Future<String> _getFilePath() async {
+    var directory = await getApplicationSupportDirectory();
+    return path.join(directory.path, 'shared_preferences.json');
+  }
+
+  _writeTestFile(String value) async {
+    fs.file(await _getFilePath())
+      ..createSync(recursive: true)
+      ..writeAsStringSync(value);
+  }
+
+  Future<String> _readTestFile() async {
+    return fs.file(await _getFilePath()).readAsStringSync();
+  }
+
+  SharedPreferencesLinux _getPreferences() {
+    var prefs = SharedPreferencesLinux();
+    prefs.fs = fs;
+    return prefs;
+  }
+
+  test('getAll', () async {
+    await _writeTestFile('{"key1": "one", "key2": 2}');
+    var prefs = _getPreferences();
+
+    var values = await prefs.getAll();
+    expect(values, hasLength(2));
+    expect(values['key1'], 'one');
+    expect(values['key2'], 2);
+  });
+
+  test('remove', () async {
+    await _writeTestFile('{"key1":"one","key2":2}');
+    var prefs = _getPreferences();
+
+    await prefs.remove('key2');
+
+    expect(await _readTestFile(), '{"key1":"one"}');
+  });
+
+  test('setValue', () async {
+    await _writeTestFile('{}');
+    var prefs = _getPreferences();
+
+    await prefs.setValue('', 'key1', 'one');
+    await prefs.setValue('', 'key2', 2);
+
+    expect(await _readTestFile(), '{"key1":"one","key2":2}');
+  });
+
+  test('clear', () async {
+    await _writeTestFile('{"key1":"one","key2":2}');
+    var prefs = _getPreferences();
+
+    await prefs.clear();
+    expect(await _readTestFile(), '{}');
+  });
+}
diff --git a/script/build_all_plugins_app.sh b/script/build_all_plugins_app.sh
index 2c0a906..262c4ed 100755
--- a/script/build_all_plugins_app.sh
+++ b/script/build_all_plugins_app.sh
@@ -26,6 +26,7 @@
   "path_provider_platform_interface"
   "path_provider_web"
   "plugin_platform_interface"
+  "shared_preferences_linux"
   "shared_preferences_macos"
   "shared_preferences_platform_interface"
   "shared_preferences_web"